Building a RESTful API with Node.js, Express, Sequelize & PostgreSQL (2025 Update)

by Didin J. on Jul 27, 2025 Building a RESTful API with Node.js, Express, Sequelize & PostgreSQL (2025 Update)

Build a modern RESTful API with Node.js, Express, Sequelize, and PostgreSQL. Updated for 2025 with validation, best practices, and testing tips.

In this comprehensive guide, you’ll learn how to build a modern RESTful API using:

  • Node.js 20+

  • Express.js 4+

  • Sequelize 6+

  • PostgreSQL 14+

  • Modern tooling: dotenv, ES modules, Sequelize CLI, Docker, and Jest for testing.

By the end, you’ll have a robust, scalable API with a structured project, migrations, error handling, and testing.

Prerequisites

Make sure you have the following installed:

  • Node.js (v18+ recommended)

  • PostgreSQL (v14+)

  • npm

  • Docker (optional, for containerized development)


Step 1: Project Initialization

Create and navigate to your project folder:

mkdir node-express-sequelize-api
cd node-express-sequelize-api

Initialize a new Node.js project:

npm init -y

Leave type as default:

"type": "commonjs",


Step 2: Install Dependencies

Install core and development dependencies:

npm install express sequelize pg pg-hstore dotenv cors helmet
npm install --save-dev nodemon sequelize-cli


Step 3: Initialize Sequelize Project

npx sequelize-cli init

This creates:

├── config
│   └── config.json
├── models
├── migrations
├── seeders

Update config/config.js:

{
  "development": {
    "username": "djamware",
    "password": "dj@mw@r3",
    "database": "node_api",
    "host": "127.0.0.1",
    "dialect": "postgres"
  }
}

Create a new PostgreSQL database:

psql postgres -U djamware
create database node_api;
\q


Step 4: Create Models and Migrations

Let’s start with a User and Post model.

npx sequelize-cli model:generate --name User --attributes name:string,email:string
npx sequelize-cli model:generate --name Post --attributes title:string,content:text,userId:integer

Run migrations:

npx sequelize-cli db:migrate


Step 5: Define Associations in Models

models/user.js

"use strict";
const { Model } = require("sequelize");
module.exports = (sequelize, DataTypes) => {
  class User extends Model {
    /**
     * Helper method for defining associations.
     * This method is not a part of Sequelize lifecycle.
     * The `models/index` file will call this method automatically.
     */
    static associate(models) {
      // define association here
    }
  }
  User.init(
    {
      name: DataTypes.STRING,
      email: DataTypes.STRING
    },
    {
      sequelize,
      modelName: "User"
    }
  );
  User.associate = models => {
    User.hasMany(models.Post, { foreignKey: "userId" });
  };
  return User;
};

models/post.js

"use strict";
const { Model } = require("sequelize");
module.exports = (sequelize, DataTypes) => {
  class Post extends Model {
    /**
     * Helper method for defining associations.
     * This method is not a part of Sequelize lifecycle.
     * The `models/index` file will call this method automatically.
     */
    static associate(models) {
      // define association here
    }
  }
  Post.init(
    {
      title: DataTypes.STRING,
      content: DataTypes.TEXT,
      userId: DataTypes.INTEGER
    },
    {
      sequelize,
      modelName: "Post"
    }
  );
  Post.associate = models => {
    Post.belongsTo(models.User, { foreignKey: "userId" });
  };
  return Post;
};


Step 6: Set Up Express Server

Create a server.js file:

import express from 'express';
import cors from 'cors';
import helmet from 'helmet';
import dotenv from 'dotenv';
import db from './models/index.js';

dotenv.config();

const app = express();
const PORT = process.env.PORT || 5000;

app.use(helmet());
app.use(cors());
app.use(express.json());

// Basic test route
app.get('/', (req, res) => {
  res.json({ message: 'API is running.' });
});

// Sync DB (in production, use migrations instead)
db.sequelize.sync().then(() => {
  app.listen(PORT, () => {
    console.log(`Server is running on http://localhost:${PORT}`);
  });
});


Step 7: Create Controllers and Routes

We’ll start with a UserController to handle basic CRUD operations and connect it to routes.

📁 Project Structure (Relevant Parts)

├── controllers/
│   └── user.controller.js
├── routes/
│   └── user.routes.js
├── models/
│   └── user.js
├── server.js

🧠 1. Create controllers/user.controller.js

const db = require("../models");
const User = db.User;

// Create new user
exports.create = async (req, res) => {
  try {
    const { name, email } = req.body;
    const user = await User.create({ name, email });
    res.status(201).json(user);
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
};

// Get all users
exports.findAll = async (_req, res) => {
  try {
    const users = await User.findAll();
    res.json(users);
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
};

// Get a single user
exports.findOne = async (req, res) => {
  try {
    const user = await User.findByPk(req.params.id);
    if (!user) return res.status(404).json({ message: "User not found" });
    res.json(user);
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
};

// Update a user
exports.update = async (req, res) => {
  try {
    const { name, email } = req.body;
    const [updated] = await User.update(
      { name, email },
      {
        where: { id: req.params.id }
      }
    );
    if (!updated) return res.status(404).json({ message: "User not found" });
    const updatedUser = await User.findByPk(req.params.id);
    res.json(updatedUser);
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
};

// Delete a user
exports.delete = async (req, res) => {
  try {
    const deleted = await User.destroy({ where: { id: req.params.id } });
    if (!deleted) return res.status(404).json({ message: "User not found" });
    res.json({ message: "User deleted" });
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
};

🔗 2. Create routes/user.routes.js

const express = require('express');
const router = express.Router();
const userController = require('../controllers/user.controller');

// Base route: /api/users
router.post('/', userController.create);
router.get('/', userController.findAll);
router.get('/:id', userController.findOne);
router.put('/:id', userController.update);
router.delete('/:id', userController.delete);

module.exports = router;

🚀 3. Register Routes in app.js

Update your server.js or main server file:

const express = require('express');
const app = express();
const db = require('./models');
const userRoutes = require('./routes/user.routes');

app.use(express.json());
app.use('/api/users', userRoutes);

// Sync database and start server
const PORT = process.env.PORT || 3000;
db.sequelize.sync().then(() => {
  app.listen(PORT, () => {
    console.log(`Server is running on http://localhost:${PORT}`);
  });
});

✅ Example Endpoints

Method URL Description
GET /api/users List all users
POST /api/users Create a user
GET /api/users/:id Get one user
PUT /api/users/:id Update a user
DELETE /api/users/:id Delete a user


Step 8: Running and Testing the REST API

Now that your routes and controllers are ready, let's test your RESTful API locally using Postman, cURL, or any API client.

🟢 1. Start the Server

node server.js

If everything is set correctly, you should see:

Server is running on http://localhost:3000

And PostgreSQL tables (like Users) should be automatically created from the Sequelize model/migration

📬 2. Test the API Endpoints

You can use Postman, Insomnia, or just curl.

✅ a. Create User

POST /api/users
Body (JSON):

{
  "name": "Alice",
  "email": "[email protected]"
}

Response:

{
  "id": 1,
  "name": "Alice",
  "email": "[email protected]",
  "createdAt": "...",
  "updatedAt": "..."
}

b. Get All Users

GET /api/users

Response:

[
  {
    "id": 1,
    "name": "Alice",
    "email": "[email protected]",
    "createdAt": "...",
    "updatedAt": "..."
  }
]

✅ c. Get One User

GET /api/users/1

✅ d. Update User

PUT /api/users/1
Body:

{
  "name": "Alice Updated",
  "email": "[email protected]"
}

✅ e. Delete User

DELETE /api/users/1

Response:

{ "message": "User deleted" }

✅ cURL Testing (Optional)

If you prefer curl, here's an example:

curl -X POST http://localhost:3000/api/users \
  -H "Content-Type: application/json" \
  -d '{"name":"Test","email":"[email protected]"}'

📦 Bonus: Run with Nodemon (for auto-restart)

Install:

npm install --save-dev nodemon

Update your package.json:

"scripts": {
  "start": "node app.js",
  "dev": "nodemon app.js"
}

Then run:

npm run dev


Step 9: Validation with express-validator

To ensure your API receives valid data, we’ll integrate express-validator, a powerful middleware for validating and sanitizing inputs.

📦 1. Install express-validator

npm install express-validator

🛡️ 2. Create a Validation Middleware

Let’s define a validation file for users:

mkdir middlewares
touch middlewares/user.validation.js
// middlewares/user.validation.js
const { body } = require('express-validator');

exports.validateUser = [
  body('name')
    .notEmpty()
    .withMessage('Name is required'),
  body('email')
    .isEmail()
    .withMessage('Valid email is required'),
];

🧠 3. Handle Validation Errors

Create a reusable middleware to capture validation failures:

const { validationResult } = require('express-validator');

module.exports = (req, res, next) => {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    return res
      .status(400)
      .json({ errors: errors.array().map(err => err.msg) });
  }
  next();
};

🔗 4. Apply Validation to Routes

Now apply the validation to your POST and PUT routes:

const express = require('express');
const router = express.Router();
const userController = require('../controllers/user.controller');
const { validateUser } = require('../middlewares/user.validation');
const validate = require('../middlewares/validate');

// Base route: /api/users
router.post('/', validateUser, validate, userController.create);
router.put('/:id', validateUser, validate, userController.update);
router.get('/', userController.findAll);
router.get('/:id', userController.findOne);
router.delete('/:id', userController.delete);

module.exports = router;

🧪 5. Try Sending Invalid Data

POST /api/users

{
  "name": "",
  "email": "invalid-email"
}

Response:

{
  "errors": [
    "Name is required",
    "Valid email is required"
  ]
}

✅ With that, you’ve added input validation and basic error handling to your RESTful API.


Conclusion

In this comprehensive guide, you’ve built a modern RESTful API using Node.js, Express.js, Sequelize, and PostgreSQL — fully updated for the 2025 ecosystem. You learned how to:

  • Set up a new Node.js project with Sequelize and PostgreSQL

  • Define models and migrations using Sequelize CLI

  • Create clean and modular controllers and routes

  • Implement input validation with express-validator

  • Test your endpoints using Postman or curl

With this solid foundation, you're ready to expand your API with associations, authentication, pagination, unit testing, or even connect it to a frontend built with Angular, React, or Vue.

If you'd like to continue building, here are some great next steps:

  • 🔐 Add JWT authentication

  • 🧪 Write unit and integration tests with Jest or Mocha

  • 📦 Deploy to cloud platforms like Heroku, Vercel, or Fly.io

  • 📘 Document your API with Swagger (OpenAPI)

 

You can get the full working source code on our GitHub.

That's just the basics. If you need more deep learning about Node.js, Express.js, PostgreSQL, Vue.js, and GraphQL, or related you can take the following cheap course:

Thanks!