The comprehensive step-by-step tutorial on building a secure Node.js, Express.js, Passport.js, and PostgreSQL Restful Web Service. Previously, we have shown you a combination of Node.js, Express.js, and PostgreSQL tutorial. Now, we just add security for those REST API Web Service endpoints. Of course, we will start this tutorial from scratch, or a zero application. We will use JWT for this Node.js, Express.js, Passport.js, and PostgreSQL tutorial.
🧰 Prerequisites
-
Node.js 20+
-
PostgreSQL 14+
-
Basic knowledge of Express.js & REST APIs
-
curl
or Postman for testing
Step 1: Initialize Your Project
Using Express.js with a myriad of HTTP utility methods and middleware at your disposal, creating a robust API is quick and easy. Go to your Node project folder, then create a new Node application folder.
mkdir secure-node-api && cd secure-node-api
npm init -y
Enable ES Module Support
In your package.json
, add/modify:
"type": "module",
Install Dependencies
npm install express sequelize pg pg-hstore passport passport-jwt jsonwebtoken bcrypt dotenv
npm install -D nodemon sequelize-cli
Step 2: Set up Sequelize
Sequelize is a promise-based Node.js ORM for Postgres, MySQL, MariaDB, SQLite, and Microsoft SQL Server. It features solid transaction support, relations, eager and lazy loading, read replication and more. Before installing the modules for this project, first, initialize Sequelize.
npx sequelize-cli init
Configure config/config.json
:
{
"development": {
"username": "djamware",
"password": "dj@mw@r3",
"database": "secure_api_db",
"host": "127.0.0.1",
"dialect": "postgres"
}
}
Create Database
psql postgres -U djamware
create database secure_api_db;
\q
Create User Model
npx sequelize-cli model:generate --name User --attributes username:string,password:string
npx sequelize-cli db:migrate
Update models/index.js for ESM
Edit models/index.js
:
import { Sequelize } from 'sequelize';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import dotenv from 'dotenv';
dotenv.config();
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const basename = path.basename(__filename);
const env = process.env.NODE_ENV || 'development';
const config = JSON.parse(fs.readFileSync(path.join(__dirname, '../config/config.json')))[env];
const sequelize = new Sequelize(config.database, config.username, config.password, config);
const db = {};
const modelFiles = fs
.readdirSync(__dirname)
.filter(
file => file.indexOf('.') !== 0 && file !== basename && file.slice(-3) === '.js'
);
for (const file of modelFiles) {
const model = await import(path.join(__dirname, file));
const definedModel = model.default(sequelize, Sequelize.DataTypes);
db[definedModel.name] = definedModel;
}
Object.keys(db).forEach(modelName => {
if (db[modelName].associate) {
db[modelName].associate(db);
}
});
db.sequelize = sequelize;
db.Sequelize = Sequelize;
export default db;
Update models/user.js for ESM
Edit models/user.js
:
export default (sequelize, DataTypes) => {
const User = sequelize.define('User', {
username: DataTypes.STRING,
password: DataTypes.STRING
});
User.associate = function(models) {
// associations can be defined here
};
return User;
};
Step 3: Auth Helpers (Password Hashing)
Create utils/auth.js
:
import bcrypt from 'bcrypt';
const SALT_ROUNDS = 10;
export const hashPassword = pw => bcrypt.hash(pw, SALT_ROUNDS);
export const comparePassword = (pw, hash) => bcrypt.compare(pw, hash);
Step 4: Configure Passport JWT
Create config/passport.js
:
import passport from 'passport';
import { Strategy as JwtStrategy, ExtractJwt } from 'passport-jwt';
import { User } from '../models/index.js';
import dotenv from 'dotenv';
dotenv.config();
passport.use(new JwtStrategy({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: process.env.JWT_SECRET
}, async (payload, done) => {
try {
const user = await User.findByPk(payload.id);
return done(null, user || false);
} catch (err) {
return done(err, false);
}
}));
export default passport;
Create .env
file:
PORT=3000
JWT_SECRET=your_super_secret_jwt_key
Step 5: Auth Routes
Create routes/auth.js
:
import express from 'express';
import jwt from 'jsonwebtoken';
import passport from '../config/passport.js';
import db from '../models/index.js';
const { User } = db;
import { hashPassword, comparePassword } from '../utils/auth.js';
import dotenv from 'dotenv';
dotenv.config();
const router = express.Router();
router.post('/signup', async (req, res) => {
const { username, password } = req.body;
const hashed = await hashPassword(password);
const user = await User.create({ username, password: hashed });
res.json({ id: user.id, username: user.username });
});
router.post('/signin', async (req, res) => {
const { username, password } = req.body;
const user = await User.findOne({ where: { username } });
if (!user || !(await comparePassword(password, user.password))) {
return res.status(401).json({ message: 'Invalid credentials' });
}
const token = jwt.sign({ id: user.id }, process.env.JWT_SECRET, { expiresIn: '1h' });
res.json({ token });
});
router.get('/profile', passport.authenticate('jwt', { session: false }), (req, res) => {
res.json({ id: req.user.id, username: req.user.username });
});
export default router;
Step 6: Create App Entry
Create app.js
:
import express from 'express';
import passport from './config/passport.js';
import authRoutes from './routes/auth.js';
import dotenv from 'dotenv';
dotenv.config();
const app = express();
app.use(express.json());
app.use(passport.initialize());
app.use('/api/auth', authRoutes);
const port = process.env.PORT || 3000;
app.listen(port, () => console.log(`Server running on http://localhost:${port}`));
Start with:
npx nodemon app.js
(Optional) OAuth2 Integration
Add OAuth2 dependencies:
npm install express-oauth-server
Integrate OAuth2 following this structure:
import OAuthServer from 'express-oauth-server';
app.oauth = new OAuthServer({ model: require('./oauthModel.js') });
app.post('/oauth/token', app.oauth.token());
app.get('/secure', app.oauth.authenticate(), (req, res) => res.send('Secure Data'));
Create oauthModel.js (Optional OAuth2 Support)
Create oauthModel.js
:
export default {
getAccessToken: async function(token) {
// Implement token retrieval logic
return { accessToken: token, client: {}, user: {}, accessTokenExpiresAt: new Date(Date.now() + 3600 * 1000) };
},
getClient: async function(clientId, clientSecret) {
return { id: clientId, grants: ['password'] };
},
saveToken: async function(token, client, user) {
return { ...token, client, user };
},
getUser: async function(username, password) {
// Validate and return a user
return { id: 1, username };
}
};
Testing Endpoints
Use Postman or curl:
curl -X POST http://localhost:3000/api/auth/signup -H "Content-Type: application/json" -d '{"username":"test","password":"1234"}'
Response:
{"id":1,"username":"admin"}
Sign in and get JWT, then access the profile:
curl -X POST http://localhost:3000/api/auth/signin \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"123456"}'
Response:
{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWF0IjoxNzUyODM3NzAxLCJleHAiOjE3NTI4NDEzMDF9.d2d2KHB5mF_bPhelTafp7g8ScTnzbEX7JL8pITEg1uw"}
Access secure route:
curl http://localhost:3000/api/auth/profile \
-H "Authorization: Bearer <your_token_here>"
Response:
{"id":1,"username":"admin"}
Test OAuth2 token grant (optional):
curl -X POST http://localhost:3000/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=password&username=admin&password=123456&client_id=abc&client_secret=123"
Conclusion
You've now created a secure, modern REST API using Node.js, Express, PostgreSQL, Sequelize, JWT, and optionally OAuth2. Ready to integrate into any frontend or mobile app!
You can get the working source code on our GitHub.
That's just the basics. If you need more deep learning about Node, Express, Sequelize, PostgreSQL/MySQL, or related, you can take the following cheap course:
- Sequelize ORM with NodeJS
- Angular + NodeJS + PostgreSQL + Sequelize
- PostgreSQL 2019 MasterClass
- ORDBMS With PostgreSQL Essential Administration Training
- Node. js: Node. js App Development - Novice to Pro!: 4-in-1
Thanks!