# How to create a REST API with Node.js and Express

### Quick answer: How to Build a REST API with Node.js and Express

Building a REST API with Node.js and Express is one of the most practical skills in modern web development. Node.js powers millions of backends for web and mobile apps, while Express provides the flexibility and simplicity developers need to move fast.

An API is a contract that facilitates communication between applications, systems, and teams. Whether you’re creating a user management service, a mobile backend, or microservices for a larger product, the key is building APIs that are structured, secure, and testable from the start.

- **Design:** Define clear, consistent endpoints that follow REST principles.
- **Test:** Validate requests and responses using tools like Postman.
- **Deploy:** Use environments and CI/CD pipelines for smooth releases.
- **Observe:** Monitor and iterate using logs, metrics, and feedback.
 
*In short:* You’ll build a complete user management API from scratch, learning how to structure routes, implement authentication, and test endpoints using Postman**,** the industry standard for API collaboration and testing.

[ Try Postman today →](https://identity.getpostman.com/signup)

 

## Build a REST API

We’ll create a REST API for user management with the following endpoints:

 | Endpoint | Method | Purpose |
|---|---|---|
| `/signup` | POST | Register a new user |
| `/login` | POST | Authenticate and issue a JWT |
| `/user` | GET | Retrieve a user’s profile |
| `/user/:userId` | PATCH | Update user details |
| `/user/all` | GET | List all users (admin only) |
| `/user/change-role/:userId` | PATCH | Change user roles (admin only) |
| `/user/:userId` | DELETE | Delete a user (admin only) |

These endpoints cover the most common REST operations: Create, Read, Update, and Delete (CRUD), as well as authentication and role-based permissions.

## Why Node.js and Express?

Node.js is ideal for building [REST APIs](https://blog.postman.com/rest-api-examples/):

- You write JavaScript across your stack, so there’s no need to switch languages.
- It handles multiple concurrent requests efficiently through its event-driven architecture.
- The npm ecosystem provides packages for nearly any feature, from authentication to database connectors.
 
Express builds on this with a minimal, unopinionated framework that makes it easy to define routes, use middleware, and scale as your application grows.

Together, they’re a natural fit for rapid, iterative development. And when combined with Postman as a collaborative API platform, you can test, document, and share your endpoints instantly.

## Prerequisites

Before we start, ensure you have:

- Node.js installed ([download here](https://nodejs.org/ "https://nodejs.org/"))
- A code editor like VS Code
- Basic JavaScript knowledge
- A Postman account for testing and documenting your API
 
**Tip:** [Postman Collections](https://learning.postman.com/docs/collections/collections-overview/ "https://learning.postman.com/docs/collections/collections-overview/") let you organize and save API requests. As you build, you can create a shared collection of all your endpoints and use them as executable API documentation.

## Step 1: Initialize your project

Open a terminal and run:

 ```
mkdir user-api
cd user-api
npm init -y
```

This creates a `package.json` file that tracks dependencies and scripts for your project.

## Step 2: Install dependencies

 ```
npm install express sequelize sqlite3 jsonwebtoken ajv
```

Here’s what each package does:

- `express` → Web framework for building routes and handling requests
- `sequelize` → ORM for interacting with databases
- `sqlite3` → Lightweight SQL database for development
- `jsonwebtoken` → Generates and verifies JSON Web Tokens for authentication
- `ajv` → Validates incoming request data against a defined schema
 
**Tip:** If you’re testing in multiple contexts (local, staging, production), use [environments](https://learning.postman.com/docs/developer/vs-code-extension/manage-environments/ "https://learning.postman.com/docs/developer/vs-code-extension/manage-environments/") in Postman to switch between base URLs and tokens without manually editing requests.

## Step 3: Create a basic server

Create a new file named `app.js`:

 ```
const express = require('express');
const app = express();

app.use(express.json());

app.get('/status', (req, res) => {
  res.json({
    status: 'Running',
    timestamp: new Date().toISOString()
  });
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
```

Start your server:

 ```
node app.js
```

Now open Postman and send a GET request to `http://localhost:3000/status`.

You should see a JSON response with the current timestamp.

## Step 4: Structure your REST API

Organize your files for clarity and scalability:

 ```
user-api/
├── app.js
├── authorization/
│   ├── routes.js
│   └── controller.js
├── users/
│   ├── routes.js
│   └── controller.js
├── common/
│   ├── models/
│   │   └── User.js
│   └── middlewares/
│       ├── IsAuthenticated.js
│       └── CheckPermission.js
└── storage/
    └── data.db
```

This structure separates concerns: controllers handle logic, routes handle requests, and middleware enforces access control. It mirrors the Design → Build → Test → Deploy stages of the modern [API lifecycle](https://www.postman.com/api-platform/api-lifecycle/).

## Step 5: Define your data model and connect the database

Create `common/models/User.js`:

 ```
const { DataTypes } = require('sequelize');

const UserModel = {
  id: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true },
  username: { type: DataTypes.STRING, allowNull: false, unique: true },
  email: { type: DataTypes.STRING, allowNull: false, unique: true },
  password: { type: DataTypes.STRING, allowNull: false },
  firstName: { type: DataTypes.STRING, allowNull: false },
  lastName: { type: DataTypes.STRING, allowNull: false },
  role: { type: DataTypes.STRING, defaultValue: 'USER' },
  age: { type: DataTypes.INTEGER }
};

module.exports = (sequelize) => sequelize.define('user', UserModel);
```

### Initialize the database

Next, create `common/database.js`:

 ```
const { Sequelize } = require('sequelize');
const sequelize = new Sequelize({
  dialect: 'sqlite',
  storage: './storage/data.db'
});
module.exports = sequelize;
```

Import this file into your main `app.js` and sync models with:

 ```
const sequelize = require('./common/database');
const defineUser = require('./common/models/User');
const User = defineUser(sequelize);

sequelize.sync();
```

This creates the SQLite database on first run.

**Tip:** You can use [pre-request scripts](https://learning.postman.com/docs/tests-and-scripts/write-scripts/pre-request-scripts/ "https://learning.postman.com/docs/tests-and-scripts/write-scripts/pre-request-scripts/") to seed or reset data before each run. This is particularly handy for regression testing across environments.

## Step 6: Create a signup endpoint

Create `authorization/controller.js`:

 ```
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
const sequelize = require('../common/database');
const defineUser = require('../common/models/User');
const User = defineUser(sequelize);

const encryptPassword = (password) =>
  crypto.createHash('sha256').update(password).digest('hex');

const generateAccessToken = (username, userId) =>
  jwt.sign({ username, userId }, 'your-secret-key', { expiresIn: '24h' });

exports.register = async (req, res) => {
  try {
    const { username, email, password, firstName, lastName, age } = req.body;
    const encryptedPassword = encryptPassword(password);
    const user = await User.create({
      username,
      email,
      password: encryptedPassword,
      firstName,
      lastName,
      age
    });
    const accessToken = generateAccessToken(username, user.id);
    res.status(201).json({
      success: true,
      user: { id: user.id, username: user.username, email: user.email },
      token: accessToken
    });
  } catch (err) {
    res.status(500).json({ success: false, error: err.message });
  }
};
```

Create `authorization/routes.js`:

 ```
const router = require('express').Router();
const AuthController = require('./controller');
router.post('/signup', AuthController.register);
module.exports = router;
```

Update `app.js`:

 ```
const authRoutes = require('./authorization/routes');
app.use('/', authRoutes);
```

Test in Postman with:

 ```
POST http://localhost:3000/signup
```

and this body:

 ```
{
  "username": "developer",
  "email": "dev@example.com",
  "password": "securepass123",
  "firstName": "Dev",
  "lastName": "User",
  "age": 28
}
```

You should receive a success response and JWT token.

**Tip:** Store your JWT as an [environment variable](https://learning.postman.com/docs/developer/vs-code-extension/manage-environments/#add-environment-variables "https://learning.postman.com/docs/developer/vs-code-extension/manage-environments/#add-environment-variables") or in your [Postman Vault](https://learning.postman.com/docs/sending-requests/postman-vault/postman-vault-secrets/ "https://learning.postman.com/docs/sending-requests/postman-vault/postman-vault-secrets/"), not in raw requests.

### Validate incoming data with JSON Schema

Before creating users, validate inputs using `ajv`:

 ```
const Ajv = require('ajv');
const ajv = new Ajv();
const schema = {
  type: 'object',
  required: ['username', 'email', 'password'],
  properties: {
    username: { type: 'string', minLength: 3 },
    email: { type: 'string', format: 'email' },
    password: { type: 'string', minLength: 6 }
  }
};
const validate = ajv.compile(schema);
```

Add this at the top of your signup controller:

 ```
if (!validate(req.body)) {
  return res.status(400).json({ error: 'Invalid input', details: validate.errors });
}
```

Schema validation helps ensure consistency and security.

**Tip:** You can use Postman [test scripts](https://learning.postman.com/docs/tests-and-scripts/write-scripts/test-scripts/ "https://learning.postman.com/docs/tests-and-scripts/write-scripts/test-scripts/") to automatically validate these schemas, ensuring contract compliance across teams and environments.

## Step 7: Implement authentication middleware

Create `common/middlewares/IsAuthenticated.js`:

 ```
const jwt = require('jsonwebtoken');

exports.check = (req, res, next) => {
  const authHeader = req.headers['authorization'];
  if (!authHeader)
    return res.status(401).json({ error: 'No authorization header provided' });

  const [type, token] = authHeader.split(' ');
  if (type !== 'Bearer')
    return res.status(401).json({ error: 'Invalid authorization format' });

  try {
    const decoded = jwt.verify(token, 'your-secret-key');
    req.user = decoded;
    next();
  } catch {
    res.status(401).json({ error: 'Invalid or expired token' });
  }
};
```

This middleware protects endpoints by verifying JWTs. You can test it in Postman by adding the token to the Authorization header (`Bearer <token>`).

## Step 8: Create protected user routes

Create `users/controller.js`:

 ```
const sequelize = require('../common/database');
const defineUser = require('../common/models/User');
const User = defineUser(sequelize);

exports.getUser = async (req, res) => {
  const user = await User.findByPk(req.user.userId);
  if (!user) return res.status(404).json({ error: 'User not found' });
  res.json({ success: true, data: user });
};

exports.getAllUsers = async (req, res) => {
  const users = await User.findAll();
  res.json({ success: true, data: users });
};
```

Create `users/routes.js`:

 ```
const router = require('express').Router();
const UserController = require('./controller');
const { check } = require('../common/middlewares/IsAuthenticated');

router.get('/', check, UserController.getUser);
router.get('/all', check, UserController.getAllUsers);

module.exports = router;
```

Add to `app.js`:

 ```
const userRoutes = require('./users/routes');
app.use('/user', userRoutes);
```

Now your `/user` and `/user/all` routes require a valid token.

**Tip:** Postman [workspaces](https://learning.postman.com/docs/collaborating-in-postman/using-workspaces/overview/ "https://learning.postman.com/docs/collaborating-in-postman/using-workspaces/overview/") enable developers and QA teams to test endpoints collaboratively, leave comments, and document findings in real time.

## Step 9: Add role-based permissions

Create `common/middlewares/CheckPermission.js`:

 ```
const sequelize = require('../database');
const defineUser = require('../models/User');
const User = defineUser(sequelize);

exports.has = (requiredRole) => async (req, res, next) => {
  const user = await User.findByPk(req.user.userId);
  if (!user || user.role !== requiredRole) {
    return res.status(403).json({ error: `Requires ${requiredRole} role` });
  }
  next();
};
```

Then apply this middleware to admin routes. For example, `GET /user/all`.

This is an example of security built into every workflow, which is a core principle of API maturity.

## Step 10: Create a login endpoint

Add this to `authorization/controller.js`:

 ```
exports.login = async (req, res) => {
  const { username, password } = req.body;
  const encrypted = encryptPassword(password);
  const user = await User.findOne({ where: { username } });

  if (!user || user.password !== encrypted)
    return res.status(401).json({ error: 'Invalid credentials' });

  const token = generateAccessToken(username, user.id);
  res.json({ success: true, user, token });
};
```

And in `authorization/routes.js`:

 ```
router.post('/login', AuthController.login);
```

This endpoint verifies credentials and issues a JWT token for authenticated sessions.

## Step 11: Handle errors gracefully

Add this to the end of `app.js`:

 ```
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({
    success: false,
    error: 'Something went wrong'
  });
});
```

Consistent error responses make debugging and testing easier.

**Tip:** Use Postman’s built-in test scripts to verify status codes and message structures across all endpoints.

### Monitor and observe your API

After your API is deployed, continuous testing ensures it remains reliable.

[Postman Monitors](https://learning.postman.com/docs/monitoring-your-api/intro-monitors/ "https://learning.postman.com/docs/monitoring-your-api/intro-monitors/") can automatically run your collections on a schedule, validating uptime, latency, and response accuracy. You’ll get alerts in Slack or email if something fails, so you can catch issues before customers do.

For deeper visibility, [Postman Insights](https://learning.postman.com/docs/insights/overview/ "https://learning.postman.com/docs/insights/overview/") surfaces endpoint performance data, including error rates and response times, providing teams with early insight into bottlenecks. These capabilities bring observability into the development workflow, which is essential for maintaining production-grade APIs.

## Step 12: Next steps

You’ve built a complete REST API with secure authentication, validation, and role-based access. From here, you can:

- **Document and share it**. Generate auto-updating documentation in Postman to make your endpoints easy for others to explore.
- **Automate your testing**. Use the Collection Runner or Postman CLI in your CI/CD pipeline to ensure every build passes validation.
- **Monitor in production**. Maintain the reliability of your API by setting up monitors to regularly check uptime and performance.
- **Scale your ecosystem**. Add your API to your [Private API Network](https://learning.postman.com/docs/collaborating-in-postman/private-api-network/overview/ "https://learning.postman.com/docs/collaborating-in-postman/private-api-network/overview/") to promote reuse and collaboration across your organization.
 
With these next steps, your API moves from a working prototype to a trusted, scalable service that others can confidently build on.

## Troubleshooting common issues

### "Cannot find module" errors

If you see:

 ```
Error: Cannot find module 'express'
```

You likely forgot to run `npm install` or are in the wrong directory. Make sure you’re in the project root (where `package.json` lives), then run:

 ```
npm install
node app.js
```

### Port already in use

If you get:

 ```
Error: listen EADDRINUSE: address already in use :::3000
```

Another process is using port 3000. You can either terminate that process or start the server on a different port.

 ```
PORT=3001 node app.js
```

On Mac/Linux, you can find and stop the process with:

 ```
lsof -ti:3000 | xargs kill
```

On Windows, you can find and stop the process with:

 ```
netstat -ano | findstr :3000
taskkill /PID <pid> /F
```

### Database "table does not exist" errors

If you see:

 ```
SQLITE_ERROR: no such table: users
```

Your database hasn’t been initialized. Ensure sequelize.sync() runs before your routes are registered. You can force a fresh sync with:

 ```
sequelize.sync({ force: true }); // WARNING: deletes all data
```

### 401 Unauthorized in Postman

Double-check your Authorization header format. It should look like:

 ```
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
```

Not:

- Missing “Bearer”: `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...`
- Extra colon: Bearer: `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...`
 
In Postman, use the **Bearer Token** authorization type instead of manually typing the header. This ensures proper formatting and token reuse across requests.

### CORS errors when testing from a browser

If your frontend can’t reach the API and shows a CORS error, enable Cross-Origin Resource Sharing:

 ```
npm install cors
```

Then add this to your `app.js`:

 ```
const cors = require('cors');
app.use(cors());
```