Skip to main content

2 posts tagged with "Express"

View All Tags

Securing Your Express REST API with Passport.js

· 6 min read
Huseyin BABAL
Software Developer

As web applications grow, secure authentication becomes essential to protect sensitive data and prevent unauthorized access. In this article, we’ll explore how to secure a Node.js API using Passport.js and JSON Web Tokens (JWT) for stateless, token-based authentication. We’ll also use PostgreSQL for persistent storage of user data, providing a robust, scalable setup ideal for modern applications.

Why Use Passport.js and JWT for Node.js?

Passport.js is a powerful, flexible middleware for handling authentication in Node.js applications. When paired with JWTs, it enables scalable, stateless authentication without the need to manage session data. JWTs are particularly useful in mobile-friendly applications where maintaining server-side sessions is impractical.

Advantages

Using Passport.js, PostgreSQL and JWTs offers several key benefits:

  • Passport.js simplifies integration of various authentication strategies.
  • JWTs allow for stateless authentication, meaning no session management overhead.
  • PostgreSQL offers a reliable, ACID-compliant database for securely storing user credentials.

In this article, we will be using PostgreSQL as our database. You can maintain your database in any database management system. For a convenient deployment option, consider cloud-based solutions like Rapidapp, which offers managed PostgreSQL databases, simplifying setup and maintenance.

tip

Create a free database in Rapidapp in seconds here

Step-by-Step Guide to Securing Your Express API

Project Initialization and Dependencies

Before diving into the code, ensure you have:

  • Node.js and npm installed.
  • PostgreSQL database ready for storing user data.
  • Basic understanding of JavaScript and Node.js.

To start, initialize a new Node.js project and install required dependencies:

mkdir express-rest-api-jwt && cd express-rest-api-jwt
npm init -y
npm install express passport passport-jwt jsonwebtoken pg bcrypt dotenv

We’re using:

  • express: Web framework for Node.js.
  • passport and passport-jwt: Middleware and JWT strategy for authentication.
  • jsonwebtoken: For generating and verifying JWTs.
  • pg: PostgreSQL client for Node.js.
  • bcrypt: For securely hashing passwords.
  • dotenv: For environment variable management.

Configuring PostgreSQL for User Data

Set up a PostgreSQL database to store user information. Connect to your PostgreSQL instance and create a new database and table. If you are using Rapidapp, the database is already created there.

CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL
);

In this setup, the users table stores a unique email and a hashed password. Next, create a .env file to manage sensitive configuration:

DATABASE_URL=postgresql://username:password@host:5432/secure_app
JWT_SECRET=your_jwt_secret_key

Remember to replace username, password, and other values with your actual credentials.

Building the User Authentication Logic

Password Hashing

To securely store user passwords, use bcrypt to hash them before saving to the database. This prevents storing plaintext passwords.

server.js
const bcrypt = require('bcrypt');
const saltRounds = 10;

// Hashing function
async function hashPassword(password) {
return await bcrypt.hash(password, saltRounds);
}

User Registration Endpoint

Create a registration endpoint to handle new user signups. Hash the user’s password and save it in the database.

server.js
const express = require('express');
const app = express();
const { Pool } = require('pg');
const pool = new Pool({ connectionString: process.env.DATABASE_URL });

app.use(express.json());

app.post('/register', async (req, res) => {
const { email, password } = req.body;
const passwordHash = await hashPassword(password);

try {
await pool.query('INSERT INTO users (email, password_hash) VALUES ($1, $2)', [email, passwordHash]);
res.status(201).json({ message: 'User registered successfully' });
} catch (error) {
res.status(500).json({ error: 'User registration failed' });
}
});

User Login and JWT Generation

Create a login endpoint to validate credentials. If valid, generate a JWT for the user.

server.js
const jwt = require('jsonwebtoken');

app.post('/login', async (req, res) => {
const { email, password } = req.body;

const result = await pool.query('SELECT * FROM users WHERE email = $1', [email]);
const user = result.rows[0];

if (user && await bcrypt.compare(password, user.password_hash)) {
const token = jwt.sign({ id: user.id, email: user.email }, process.env.JWT_SECRET, { expiresIn: '1h' });
res.json({ token });
} else {
res.status(401).json({ error: 'Invalid credentials' });
}
});

Implementing Passport.js with JWT Strategy

Configure Passport.js to use the JWT strategy. Create a Passport configuration file and define the JWT strategy using passport-jwt.

server.js
const passport = require('passport');
const { Strategy, ExtractJwt } = require('passport-jwt');

passport.use(new Strategy({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: process.env.JWT_SECRET
}, async (jwtPayload, done) => {
const result = await pool.query('SELECT * FROM users WHERE id = $1', [jwtPayload.id]);
const user = result.rows[0];
return user ? done(null, user) : done(null, false);
}));

app.use(passport.initialize());

With this configuration, Passport extracts the JWT from the Authorization header and verifies it using our secret key.

Creating Protected Routes

Now that Passport is set up, you can protect specific routes by requiring authentication. Passport will verify the JWT before allowing access to these routes.

server.js
app.get('/profile', passport.authenticate('jwt', { session: false }), (req, res) => {
res.json({ message: `Welcome ${req.user.email}` });
});

In this example, the /profile route requires a valid JWT. If authentication succeeds, the request proceeds; otherwise, it’s rejected.

Serving the API

Finally, start the Express server to serve the API:

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

app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});

Testing the Authentication Workflow

Register a New User

To create a new user, make a POST request to the /register endpoint with the user’s email and password.

curl -X POST http://localhost:3000/register \
-H "Content-Type: application/json" \
-d '{"email": "[email protected]", "password": "securepassword"}'

If successful, this will return:

{
"message": "User registered successfully"
}

Login with the Registered User

To log in, make a POST request to the /login endpoint with the same email and password. This will return a JWT if the credentials are correct.

curl -X POST http://localhost:3000/login \
-H "Content-Type: application/json" \
-d '{"email": "[email protected]", "password": "securepassword"}'

If successful, you’ll receive a response similar to this:

{
"token": "your_jwt_token_here"
}

Access the Protected Profile Endpoint

To access the protected /profile endpoint, you’ll need to include the JWT in the Authorization header as a Bearer token.

Replace your_jwt_token_here with the actual token you received from the login step:

curl -X GET http://localhost:3000/profile \
-H "Authorization: Bearer your_jwt_token_here"

If the JWT is valid, you should receive a response like:

{
"message": "Welcome [email protected]"
}

If the token is missing or invalid, you’ll likely get a 401 Unauthorized response:

{
"error": "Unauthorized"
}

Conclusion

Using Passport.js and JWT for authentication in a Node.js application provides a secure, stateless setup ideal for scaling. Combined with PostgreSQL, this setup efficiently handles user management while maintaining security best practices. With these foundations, you’re well-equipped to build secure, scalable applications.

tip

You can find the complete source code for this project on GitHub.

Building RESTful API with Express, Sequelize, and PostgreSQL

· 5 min read
Huseyin BABAL
Software Developer

Introduction

RESTful APIs are essential for enabling smooth communication between client applications and databases, allowing you to perform actions like creating, reading, updating, and deleting records. Using Node.js and Express for API development offers flexibility and scalability. Sequelize, an ORM, simplifies database interactions and also provides migration tools to ensure your PostgreSQL schema stays in sync with the API’s needs as the database evolves.

What is Sequelize?

Sequelize is a Node.js ORM that simplifies working with SQL databases like PostgreSQL. It supports migrations, making schema changes manageable, and integrates well with RESTful APIs.

Persistence Layer

In this article, we will be using PostgreSQL as our database. You can maintain your database in any database management system. For a convenient deployment option, consider cloud-based solutions like Rapidapp, which offers managed PostgreSQL databases, simplifying setup and maintenance.

tip

Create a free database in Rapidapp in seconds here

Step-by-Step Guide: Sequelize Migrations and REST API

Project Setup and Dependencies

To start, create a Node.js project and install the required packages:

mkdir node-movies-api
cd node-movies-api
npm init -y
npm install express sequelize pg pg-hstore sequelize-cli body-parser --save

Initialize Sequelize with:

npx sequelize-cli init

Now that we initialized the project, go to the folder node-movies-api and open it with your favourite IDE.

Configuring the Database

Edit config/config.json to include your database connection settings:

config/config.json
{
"development": {
"username": "your_db_user",
"password": "your_db_password",
"database": "database_name",
"host": "pg_host",
"port": "port",
"dialect": "postgres",
"dialectOptions": {
"ssl": {
"require": true,
"rejectUnauthorized": false

}
}
}
}

Creating a Migration for Movies

Generate a migration file for the Movies table:

npx sequelize-cli migration:generate --name create-movies-table

Edit the generated migration file in migrations folder:

module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable('Movies', {
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true,
},
title: {
type: Sequelize.STRING,
allowNull: false,
},
genre: {
type: Sequelize.STRING,
allowNull: false,
},
releaseDate: {
type: Sequelize.DATE,
allowNull: false,
},
rating: {
type: Sequelize.FLOAT,
},
createdAt: Sequelize.DATE,
updatedAt: Sequelize.DATE,
});
},
down: async (queryInterface) => {
await queryInterface.dropTable('Movies');
}
};

As you can see, we have up and down functions in the migration file. The up function is used to create the table, and the down function is used to drop the table.

Run the migration to create the table:

npx sequelize-cli db:migrate

Defining the Movie Model

Create a models/movie.js file to define the Movie model:

module.exports = (sequelize, DataTypes) => {
const Movie = sequelize.define('Movie', {
title: {
type: DataTypes.STRING,
allowNull: false,
},
genre: {
type: DataTypes.STRING,
allowNull: false,
},
releaseDate: {
type: DataTypes.DATE,
allowNull: false,
},
rating: {
type: DataTypes.FLOAT,
},
});
return Movie;
};

Setting Up Express and Routes

Create an index.js file to set up the Express server:

const express = require('express');
const bodyParser = require('body-parser');
const { Sequelize } = require('sequelize');
const db = require('./models');

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

app.use(bodyParser.json());

db.sequelize.sync().then(() => {
console.log('Database connected');
});

// Define routes here

app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});

Implementing the /movies Endpoint

Add a routes/movies.js file to define the CRUD operations for the /movies endpoint:

const express = require('express');
const router = express.Router();
const { Movie } = require('../models');

// Create a new movie
router.post('/', async (req, res) => {
try {
const movie = await Movie.create(req.body);
res.status(201).json(movie);
} catch (error) {
res.status(400).json({ error: error.message });
}
});

// Get all movies
router.get('/', async (req, res) => {
try {
const movies = await Movie.findAll();
res.json(movies);
} catch (error) {
res.status(500).json({ error: error.message });
}
});

// Get a movie by ID
router.get('/:id', async (req, res) => {
try {
const movie = await Movie.findByPk(req.params.id);
if (movie) {
res.json(movie);
} else {
res.status(404).json({ error: 'Movie not found' });
}
} catch (error) {
res.status(500).json({ error: error.message });
}
});

// Update a movie
router.put('/:id', async (req, res) => {
try {
const movie = await Movie.findByPk(req.params.id);
if (movie) {
await movie.update(req.body);
res.json(movie);
} else {
res.status(404).json({ error: 'Movie not found' });
}
} catch (error) {
res.status(400).json({ error: error.message });
}
});

// Delete a movie
router.delete('/:id', async (req, res) => {
try {
const movie = await Movie.findByPk(req.params.id);
if (movie) {
await movie.destroy();
res.status(204).end();
} else {
res.status(404).json({ error: 'Movie not found' });
}
} catch (error) {
res.status(500).json({ error: error.message });
}
});

module.exports = router;

In index.js, include this new route:

const movieRoutes = require('./routes/movies');
app.use('/movies', movieRoutes);

Now you can run the application with:

node index.js

Demo

Create a new movie

curl -X POST http://localhost:3000/movies \
-H "Content-Type: application/json" \
-d '{
"title": "Inception",
"genre": "Sci-Fi",
"releaseDate": "2010-07-16",
"rating": 8.8
}'

Retrieve all movies

curl -X GET http://localhost:3000/movies

Get a movie by ID

curl -X GET http://localhost:3000/movies/1

Update a movie

curl -X PUT http://localhost:3000/movies/1 \
-H "Content-Type: application/json" \
-d '{
"rating": 9.0
}'

Delete a movie

curl -X DELETE http://localhost:3000/movies/1

Best Practices for REST API with Sequelize

  • Validation: Use Sequelize’s validation features for better data integrity.
  • Error Handling: Include error messages to make debugging easier.
  • Environment Configuration: : Use environment variables for sensitive data like database credentials.

Conclusion

Integrating Sequelize migrations with a RESTful API allows for powerful database management and user interaction. This guide provides a blueprint for creating and maintaining a Node.js API that interacts seamlessly with a PostgreSQL database.

tip

You can find the complete source code for this project on GitHub.