Software Development

How to Build RESTful APIs with Node.js and Express (2026 Guide)

Build fast, scalable RESTful APIs using Node.js and Express by leveraging middleware, routing, and best practices for secure, high-performance backend development.

calender img Last update date: May 01, 2026

Quick Summary :-

In this in-depth tutorial, we explain how to build RESTful APIs using Node.js and Express efficiently. From creating endpoints to managing middleware and optimizing performance, this guide covers everything developers need to design robust APIs that support scalable and high-performing web applications.

Do you feel stuck while building RESTful APIs using Node.js?  To your rescue, we bring a quick and easy-to-understand, step-by-step guide on how to build and use the Node.js API effectively.

To your rescue, we bring a clear and easy-to-follow step-by-step guide on building RESTful APIs using Node.js and Express.

It starts by revisiting the core strengths of Node.js. Simply put, Node.js is a powerful JavaScript runtime that enables developers to build fast, scalable servers and backend APIs. Its widespread adoption is evident, with 52.2% of developers admiring Node.js and 45.5% favoring Express for API development.

Node

Compared to traditional backend technologies, Node.js delivers superior performance through its non-blocking, event-driven architecture. With frameworks like Express, along with databases and frontend libraries, developers can build efficient, secure, and production-ready REST APIs.

This guide walks you through every step from setting up your Node.js environment to designing, building, and consuming RESTful APIs, making it ideal for both beginners and experienced developers.

What Is a RESTful API?

A RESTful API (Representational State Transfer API) is an architectural style that enables communication between client and server using standard HTTP methods like GET, POST, PUT, and DELETE. It follows stateless principles, uses structured URLs, and exchanges data, most commonly in JSON format, making it lightweight, scalable, and easy to integrate across platforms.

RESTful APIs are widely used to power web applications, mobile apps, and third-party integrations due to their flexibility, performance, and simplicity.

Why Choose Node.js and Express for Building RESTful APIs?

Node.js and Express form one of the most popular stacks for building RESTful APIs because of their speed, scalability, and developer-friendly ecosystem.

  • High Performance: Node.js uses a non-blocking, event-driven architecture, allowing APIs to handle multiple requests efficiently.
  • Lightweight Framework: Express simplifies routing, middleware handling, and request-response management with minimal overhead.
  • Fast Development: With JavaScript used on both frontend and backend, teams can build APIs faster and maintain consistency.
  • Scalability: Ideal for building APIs that need to scale in real time for high-traffic applications.
  • Rich Ecosystem: A vast NPM ecosystem offers ready-made libraries for authentication, validation, security, and API documentation.

Together, Node.js and Express enable developers to build secure, high-performing, and maintainable RESTful APIs suitable for modern application development.

Also Read: Express vs Koa: Which Node.js Framework Is Best For You?

Step-by-Step Guide to Building RESTful APIs

Follow this practical, easy-to-understand guide to set up your environment, create routes, connect databases, and build fully functional RESTful APIs using Node.js and Express.

Step 1: Set up the project

First, ensure Node.js (LTS 20 or 22+) and npm are installed. You can download them from the official Node.js website and confirm installation:

bash
node -v

npm -v

Now create a new folder and initialize a Node.js project:

bash
mkdir nodejs-rest-api

cd nodejs-rest-api

# quick init with defaults

npm init -y

This creates a package.json file which will track your dependencies and scripts.

Step 2: Install Express and basic tools

Express is the de‑facto standard web framework for building REST APIs in Node.js. Install Express and a dev tool to auto‑restart the server:

bash
npm install express

npm install –save-dev nodemon

Update your package.json scripts so you can start the API quickly in dev mode:

json
{

  “name”: “nodejs-rest-api”,

  “version”: “1.0.0”,

  “description”: “Building a RESTful API with Node.js and Express”,

  “main”: “server.js”,

  “type”: “module”,

  “scripts”: {

    “start”: “node server.js”,

    “dev”: “nodemon server.js”,

    “test”: “echo \”Error: no test specified\” && exit 1″

  },

  “keywords”: [“nodejs”, “express”, “rest”, “api”],

  “author”: “Your Name”,

  “license”: “ISC”,

  “dependencies”: {

    “express”: “^4.21.0”

  },

  “devDependencies”: {

    “nodemon”: “^3.1.0”

  }

}

Using “type”: “module” lets you use modern ES module syntax (import/export) instead of older require calls, which aligns with current Node.js best practices.​

Step 3: Create a basic Express server

Create a server.js file in the project root and add the following code:

javascript
// server.js

import express from ‘express’;

const app = express();

const PORT = process.env.PORT || 3000;

// Built‑in middleware to parse JSON request bodies

app.use(express.json());

// Simple health check route

app.get(‘/’, (req, res) => {

  res.json({ message: ‘Node.js REST API is running 🚀’ });

});

app.listen(PORT, () => {

  console.log(`Server is running on http://localhost:${PORT}`);

});

This minimal setup does three important things:

  • Creates an Express app instance listening on port 3000
  • Enables JSON body parsing via express.json() so your API can accept JSON payloads
  • Exposes a root GET / endpoint that returns a simple JSON response instead of HTML

Start the development server:

bash
npm run dev

Open http://localhost:3000 in your browser or use Postman/Insomnia, and you should see a JSON response similar to:

json
{

“message”: “Node.js REST API is running 🚀”

}

At this point, you have a clean, modern foundation ready for building out RESTful endpoints such as /api/users or /api/notes using proper HTTP methods and status codes.

Step 4: Create a notes router

Instead of putting every route in server.js, use an Express Router for better structure and scalability (this is standard practice in modern Node.js APIs).

Create a new folder and file:

bash
mkdir routes

touch routes/notes.routes.js

Add the following code in routes/notes.routes.js:

javascript
// routes/notes.routes.jsimport { Router } from ‘express’;

const router = Router();

// Temporary in‑memory store (replace with DB later)

let notes = [];

let nextId = 1;

// CREATE – POST /api/notes

router.post(‘/’, (req, res) => {

  const { title, content } = req.body;

  if (!title || !content) {

    return res.status(400).json({ error: ‘Title and content are required.’ });

  }

  const note = {

    id: nextId++,

    title,

    content,

    createdAt: new Date().toISOString(),

  };

  notes.push(note);

  return res.status(201).json(note); // 201 = Created

});

// READ ALL – GET /api/notes

router.get(‘/’, (req, res) => {

  return res.status(200).json(notes);

});

// READ ONE – GET /api/notes/:id

router.get(‘/:id’, (req, res) => {

  const id = Number(req.params.id);

  const note = notes.find((n) => n.id === id);

  if (!note) {

    return res.status(404).json({ error: ‘Note not found.’ });

  }

  return res.status(200).json(note);

});

// UPDATE – PUT /api/notes/:id

router.put(‘/:id’, (req, res) => {

  const id = Number(req.params.id);

  const { title, content } = req.body;

  const index = notes.findIndex((n) => n.id === id);

  if (index === –1) {

    return res.status(404).json({ error: ‘Note not found.’ });

  }

  if (!title || !content) {

    return res.status(400).json({ error: ‘Title and content are required.’ });

  }

  notes[index] = {

    …notes[index],

    title,

    content,

    updatedAt: new Date().toISOString(),

  };

  return res.status(200).json(notes[index]);

});

// PARTIAL UPDATE – PATCH /api/notes/:id

router.patch(‘/:id’, (req, res) => {

  const id = Number(req.params.id);

  const { title, content } = req.body;

  const index = notes.findIndex((n) => n.id === id);

  if (index === –1) {

    return res.status(404).json({ error: ‘Note not found.’ });

  }

  if (title !== undefined) {

    notes[index].title = title;

  }

  if (content !== undefined) {

    notes[index].content = content;

  }

  notes[index].updatedAt = new Date().toISOString();

  return res.status(200).json(notes[index]);

});

// DELETE – DELETE /api/notes/:id

router.delete(‘/:id’, (req, res) => {

  const id = Number(req.params.id);

  const index = notes.findIndex((n) => n.id === id);

  if (index === –1) {

    return res.status(404).json({ error: ‘Note not found.’ });

  }

  notes.splice(index, 1);

  return res.status(204).send(); // 204 = No Content

});

export default router;

This router demonstrates how to structure CRUD endpoints, validates input, and uses appropriate HTTP status codes (200, 201, 400, 404, 204), which aligns with REST best practices.

Step 5: Register the router in the main server

Now, wire this router into server.js so all /api/notes requests are handled there.

Update server.js:

javascript
// server.jsimport express from ‘express’;

import notesRouter from ‘./routes/notes.routes.js’;

const app = express();

const PORT = process.env.PORT || 3000;

app.use(express.json());

// Health check

app.get(‘/’, (req, res) => {

  res.json({ message: ‘Node.js REST API is running 🚀’ });

});

// Notes API

app.use(‘/api/notes’, notesRouter);

app.listen(PORT, () => {

  console.log(`Server is running on http://localhost:${PORT}`);

});

With this, your API structure now looks like:

  • GET / – simple health check
  • POST /api/notes – create a new note
  • GET /api/notes – list all notes
  • GET /api/notes/:id – get a single note
  • PUT /api/notes/:id – replace a note
  • PATCH /api/notes/:id – partially update a note
  • DELETE /api/notes/:id – delete a note

Step 6: Test the CRUD endpoints

Use Postman, Insomnia, or any REST client to test the new routes.​​

Create a note – POST http://localhost:3000/api/notes

  • Body (JSON):
json
{

  “title”: “First note”,

  “content”: “This is my first REST API note.”

}

  • Expected: 201 Created with the created note object.

Get all notes – GET http://localhost:3000/api/notes

  • Expected: 200 OK with an array of notes.

Get a single note – GET http://localhost:3000/api/notes/1

  • Expected: 200 OK if it exists, otherwise 404 Not Found.Update a note – PUT http://localhost:3000/api/notes/1
  • Body:
json
{

  “title”: “Updated note”,

  “content”: “This note has been updated.”

}

Delete a note – DELETE http://localhost:3000/api/notes/1

  • Expected: 204 No Content if deletion is successful.

This gives your readers a clean, modern CRUD API with Express that they can run immediately, and sets you up to introduce MongoDB/Mongoose or SQL in the next section without carrying any legacy or insecure patterns.

Step 7: Install and configure Mongoose

From the project root, install Mongoose:

bash
npm install mongoose

Create a simple config file for the database connection string:

bash
mkdir config

touch config/db.js

Add the following in config/db.js:

javascript
// config/db.js

export const MONGODB_URI =

  process.env.MONGODB_URI || ‘mongodb://127.0.0.1:27017/nodejs_rest_api’;

In a real deployment, you will typically use MongoDB Atlas and store MONGODB_URI in an environment variable instead of hard‑coding it.

Now update server.js to connect to MongoDB at startup:

javascript
// server.js

import express from ‘express’;

import mongoose from ‘mongoose’;

import notesRouter from ‘./routes/notes.routes.js’;

import { MONGODB_URI } from ‘./config/db.js’;

const app = express();

const PORT = process.env.PORT || 3000;

app.use(express.json());

// Health check

app.get(‘/’, (req, res) => {

  res.json({ message: ‘Node.js REST API is running 🚀’ });

});

// Notes API (in‑memory)

app.use(‘/api/notes’, notesRouter);

// MongoDB connection

mongoose

  .connect(MONGODB_URI)

  .then(() => {

    console.log(‘Connected to MongoDB’);

    app.listen(PORT, () => {

      console.log(`Server is running on http://localhost:${PORT}`);

    });

  })

  .catch((err) => {

    console.error(‘MongoDB connection error:’, err);

    process.exit(1);

  });

This pattern ensures the server only starts listening after a successful database connection, which avoids runtime errors when handling requests.

Step 8: Define a User model with Mongoose

Create a models folder and a User model file:

bash
mkdir models

touch models/user.model.js

Add the following schema and model:

javascript
// models/user.model.js

import mongoose from ‘mongoose’;

const userSchema = new mongoose.Schema(

  {

    name: {

      type: String,

      required: true,

      trim: true,

      minlength: 2,

      maxlength: 100,

    },

    email: {

      type: String,

      required: true,

      unique: true,

      lowercase: true,

      trim: true,

    },

  },

  {

    timestamps: true, // adds createdAt and updatedAt

  }

);

const User = mongoose.model(‘User’, userSchema);

export default User;

Mongoose schemas allow you to define required fields, uniqueness, basic validation, and automatic timestamps, which simplifies backend logic and helps keep your data consistent.

Step 9: Build RESTful User endpoints

Create a separate router for user‑related endpoints:

bash
mkdir routes  # if not already created

touch routes/users.routes.js

Add the CRUD logic in routes/users.routes.js using async/await:

javascript
// routes/users.routes.js

import { Router } from ‘express’;

import User from ‘../models/user.model.js’;

const router = Router();

// CREATE – POST /api/users

router.post(‘/’, async (req, res) => {

  try {

    const { name, email } = req.body;

    if (!name || !email) {

      return res

        .status(400)

        .json({ error: ‘Name and email are required.’ });

    }

    const existing = await User.findOne({ email });

    if (existing) {

      return res.status(409).json({ error: ‘Email already in use.’ });

    }

    const user = await User.create({ name, email });

    return res.status(201).json(user);

  } catch (err) {

    console.error(‘Error creating user:’, err);

    return res.status(500).json({ error: ‘Internal server error.’ });

  }

});

// READ ALL – GET /api/users

router.get(‘/’, async (req, res) => {

  try {

    const users = await User.find().sort({ createdAt: –1 });

    return res.status(200).json(users);

  } catch (err) {

    console.error(‘Error fetching users:’, err);

    return res.status(500).json({ error: ‘Internal server error.’ });

  }

});

// READ ONE – GET /api/users/:id

router.get(‘/:id’, async (req, res) => {

  try {

    const user = await User.findById(req.params.id);

    if (!user) {

      return res.status(404).json({ error: ‘User not found.’ });

    }

    return res.status(200).json(user);

  } catch (err) {

    console.error(‘Error fetching user:’, err);

    return res.status(400).json({ error: ‘Invalid user id.’ });

  }

});

// UPDATE – PATCH /api/users/:id

router.patch(‘/:id’, async (req, res) => {

  try {

    const updates = req.body;

    const user = await User.findByIdAndUpdate(

      req.params.id,

      updates,

      { new: true, runValidators: true }

    );

    if (!user) {

      return res.status(404).json({ error: ‘User not found.’ });

    }

    return res.status(200).json(user);

  } catch (err) {

    console.error(‘Error updating user:’, err);

    return res.status(400).json({ error: ‘Invalid data or user id.’ });

  }

});

// DELETE – DELETE /api/users/:id

router.delete(‘/:id’, async (req, res) => {

  try {

    const user = await User.findByIdAndDelete(req.params.id);

    if (!user) {

      return res.status(404).json({ error: ‘User not found.’ });

    }

    return res.status(204).send();

  } catch (err) {

    console.error(‘Error deleting user:’, err);

    return res.status(400).json({ error: ‘Invalid user id.’ });

  }

});

export default router;

This implementation follows typical REST and HTTP status code conventions: 201 for creation, 200 for successful fetch/update, 204 for successful deletion without a body, and 4xx/5xx for validation and server errors.

New to RESTful API development?

Follow this practical Node.js and Express guide to build APIs faster.

Start Building APIs

Step 10: Register the User router

Finally, plug the new router into server.js:

javascript
// server.js

import express from ‘express’;

import mongoose from ‘mongoose’;

import notesRouter from ‘./routes/notes.routes.js’;

import usersRouter from ‘./routes/users.routes.js’;

import { MONGODB_URI } from ‘./config/db.js’;

const app = express();

const PORT = process.env.PORT || 3000;

app.use(express.json());

app.get(‘/’, (req, res) => {

  res.json({ message: ‘Node.js REST API is running 🚀’ });

});

app.use(‘/api/notes’, notesRouter);

app.use(‘/api/users’, usersRouter);

mongoose

  .connect(MONGODB_URI)

  .then(() => {

    console.log(‘Connected to MongoDB’);

    app.listen(PORT, () => {

      console.log(`Server is running on http://localhost:${PORT}`);

    });

  })

  .catch((err) => {

    console.error(‘MongoDB connection error:’, err);

    process.exit(1);

  });

You now have:

  • An in‑memory notes API to show fast, framework‑only CRUD
  • A database‑backed users API that demonstrates how to wire Express routes to MongoDB via Mongoose, using clean async/await patterns and robust status codes.​

Step 11: Install auth dependencies

Install JWT and bcrypt for password hashing:

bash
npm install jsonwebtoken bcrypt
  • jsonwebtoken signs and verifies tokens sent in the Authorization: Bearer <token> header
  • bcrypt hashes passwords before storing them and verifies plaintext passwords on login.

Step 12: Extend the User model for authentication

Update models/user.model.js to include a password field. Do not return this field in responses in a real app; here it’s simplified for learning.

javascript
// models/user.model.js

import mongoose from ‘mongoose’;

const userSchema = new mongoose.Schema(

  {

    name: {

      type: String,

      required: true,

      trim: true,

      minlength: 2,

      maxlength: 100,

    },

    email: {

      type: String,

      required: true,

      unique: true,

      lowercase: true,

      trim: true,

    },

    password: {

      type: String,

      required: true,

      minlength: 6,

    },

  },

  {

    timestamps: true,

  }

);

const User = mongoose.model(‘User’, userSchema);

export default User;

This schema now supports storing a hashed password for each user record.

Step 13: Create auth routes (register & login)

Create a dedicated auth router:

bash
touch routes/auth.routes.js

Add registration and login logic with bcrypt and JWT:

javascript
// routes/auth.routes.js

import { Router } from ‘express’;

import bcrypt from ‘bcrypt’;

import jwt from ‘jsonwebtoken’;

import User from ‘../models/user.model.js’;

const router = Router();

const JWT_SECRET = process.env.JWT_SECRET || ‘dev_secret_change_me’;

const JWT_EXPIRES_IN = ‘1h’;

// POST /api/auth/register

router.post(‘/register’, async (req, res) => {

  try {

    const { name, email, password } = req.body;

    if (!name || !email || !password) {

      return res

        .status(400)

        .json({ error: ‘Name, email, and password are required.’ });

    }

    const existing = await User.findOne({ email });

    if (existing) {

      return res.status(409).json({ error: ‘Email already in use.’ });

    }

    const hashedPassword = await bcrypt.hash(password, 10);

    const user = await User.create({

      name,

      email,

      password: hashedPassword,

    });

    // In production, avoid returning password even if hashed

    const safeUser = {

      id: user._id,

      name: user.name,

      email: user.email,

      createdAt: user.createdAt,

    };

    return res.status(201).json(safeUser);

  } catch (err) {

    console.error(‘Error registering user:’, err);

    return res.status(500).json({ error: ‘Internal server error.’ });

  }

});

// POST /api/auth/login

router.post(‘/login’, async (req, res) => {

  try {

    const { email, password } = req.body;

    if (!email || !password) {

      return res

        .status(400)

        .json({ error: ‘Email and password are required.’ });

    }

    const user = await User.findOne({ email });

    if (!user) {

      return res.status(401).json({ error: ‘Invalid credentials.’ });

    }

    const isMatch = await bcrypt.compare(password, user.password);

    if (!isMatch) {

      return res.status(401).json({ error: ‘Invalid credentials.’ });

    }

    const payload = {

      sub: user._id.toString(),

      email: user.email,

    };

    const accessToken = jwt.sign(payload, JWT_SECRET, {

      expiresIn: JWT_EXPIRES_IN,

    });

    return res.status(200).json({

      accessToken,

      user: {

        id: user._id,

        name: user.name,

        email: user.email,

      },

    });

  } catch (err) {

    console.error(‘Error logging in:’, err);

    return res.status(500).json({ error: ‘Internal server error.’ });

  }

});

export default router;

This flow hashes passwords on registration and returns a signed JWT on successful login, following modern guidance for secure authentication workflows.

Step 14: Add JWT verification middleware

Create a middleware that validates the JWT from the Authorization header:

bash
mkdir middlewares

touch middlewares/auth.middleware.js

 

javascript
// middlewares/auth.middleware.js

import jwt from ‘jsonwebtoken’;

const JWT_SECRET = process.env.JWT_SECRET || ‘dev_secret_change_me’;

export const requireAuth = (req, res, next) => {

  const header = req.headers[‘authorization’];

  if (!header) {

    return res.status(401).json({ error: ‘Authorization header missing.’ });

  }

  const [scheme, token] = header.split(‘ ‘);

  if (scheme !== ‘Bearer’ || !token) {

    return res.status(401).json({ error: ‘Invalid authorization format.’ });

  }

  try {

    const decoded = jwt.verify(token, JWT_SECRET);

    req.user = decoded; // attach user payload to request

    return next();

  } catch (err) {

    console.error(‘JWT verification failed:’, err);

    return res.status(403).json({ error: ‘Invalid or expired token.’ });

  }

};

This middleware applies the “Bearer token” pattern recommended for securing REST APIs with JWTs.

Step 15: Protect sensitive routes

Finally, wire the new auth routes and middleware into server.js and protect user endpoints:

javascript
// server.js

import express from ‘express’;

import mongoose from ‘mongoose’;

import notesRouter from ‘./routes/notes.routes.js’;

import usersRouter from ‘./routes/users.routes.js’;

import authRouter from ‘./routes/auth.routes.js’;

import { requireAuth } from ‘./middlewares/auth.middleware.js’;

import { MONGODB_URI } from ‘./config/db.js’;

const app = express();

const PORT = process.env.PORT || 3000;

app.use(express.json());

app.get(‘/’, (req, res) => {

  res.json({ message: ‘Node.js REST API is running 🚀’ });

});

// Public auth routes

app.use(‘/api/auth’, authRouter);

// Public notes routes (for demo)

app.use(‘/api/notes’, notesRouter);

// Protected user routes

app.use(‘/api/users’, requireAuth, usersRouter);

mongoose

  .connect(MONGODB_URI)

  .then(() => {

    console.log(‘Connected to MongoDB’);

    app.listen(PORT, () => {

      console.log(`Server is running on http://localhost:${PORT}`);

    });

  })

  .catch((err) => {

    console.error(‘MongoDB connection error:’, err);

    process.exit(1);

  });

Now the flow looks like this:

  1. Register – POST /api/auth/register with { name, email, password }
  2. Login – POST /api/auth/login to receive an accessToken
  3. Access protected routes – send Authorization: Bearer <token> to endpoints under /api/users

Conclusion

Building RESTful APIs using Node.js and Express is a powerful way to create scalable, high-performance backend systems. With its event-driven architecture and lightweight framework, Node.js enables faster development and efficient request handling.

By following the step-by-step approach in this guide from environment setup to API creation, you can confidently design secure, maintainable, and production-ready REST APIs. Whether you’re a beginner or scaling enterprise applications, Node.js and Express remain a future-proof choice in 2026 and beyond.

Looking for Node.js API experts?

eSparkBiz delivers high-performance backend APIs tailored to your business needs.

Start with eSparkBiz

 

Frequently Asked Questions

What is a Node.js API and how is it used?

A Node.js API provides an interface for building server side applications. It enables handling HTTP requests, interacting with databases and exposing endpoints for client applications, supporting fast and efficient backend development.

How do I make a basic HTTP request using Node.js API?

Use the built-in http or https modules or libraries like Axios or node fetch. Send GET or POST requests, handle responses asynchronously and manage errors to ensure reliable communication with external services.

How can I integrate Node.js API with a database?

Use database clients or ORMs such as Mongoose for MongoDB or Sequelize for SQL databases. Establish connections, execute queries or operations asynchronously and handle errors to maintain consistent application behavior.

What are best practices for handling API errors in Node.js?

Implement structured error objects, try-catch blocks for synchronous code and promise rejection handlers for asynchronous operations. Centralized logging and standardized error responses improve reliability and maintainability.

How can I secure my Node.js API?

Validate inputs, implement authentication and authorization, encrypt sensitive data and regularly audit dependencies. Protect endpoints from common attacks like injection, cross site scripting and improper access control.

Resources from your Leaders in Digital Product Builds

We are passionate about discussing recent technologies and their applications, constantly writing blogs and articles in the field. Don't miss out on our detailed and insightful write-ups. Review all our latest blogs and updates here.

.NET App Development for Enterprise: Why It’s the Ideal Choice for Large-Scale Projects
.NET App Development for Enterprise: Why It’s the Ideal Choice for Large-Scale Projects
Harikrishna Kundariya
CEO, eSparkBiz
A Comprehensive Guide to Selecting the Right Tech Stack for Mobile Apps
A Comprehensive Guide to Selecting the Right Tech Stack for Mobile Apps
Harikrishna Kundariya
CEO, eSparkBiz
A Complete Guide to Enterprise Application Integration
A Complete Guide to Enterprise Application Integration
Harikrishna Kundariya
CEO, eSparkBiz
75+ Mobile App Usage Statistics to Plan Successful App Development
75+ Mobile App Usage Statistics to Plan Successful App Development
Jigar Agrawal
Digital Growth Hacker, eSparkBiz