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+)
-
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:
- Node. JS
- Learning Express JS: Creating Future Web Apps & REST APIs
- Angular + NodeJS + PostgreSQL + Sequelize
- Build 7 Real World Applications with Vue. js
- Full-Stack Vue with GraphQL - The Ultimate Guide
Thanks!