Skip to main content

2 posts tagged with "Node.js"

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 Simple Product API with Apollo GraphQL and PostgreSQL

· 7 min read
Huseyin BABAL
Software Developer

Introduction

GraphQL has transformed how we build APIs by offering a flexible and powerful alternative to REST. It enables clients to request exactly the data they need, making it a great fit for modern applications. In this article, we'll explore GraphQL, its use cases, and key concepts like queries, mutations, and resolvers. We'll also walk you through building a simple product API using Apollo GraphQL with RapidApp PostgreSQL as your datasource.

What is GraphQL?

GraphQL is a query language for your API that allows clients to request specific data, avoiding over-fetching or under-fetching. Unlike REST, where multiple endpoints might be needed, GraphQL provides a single endpoint through which clients can access multiple data sources and types with one request.

Use Cases

GraphQL is widely used for:

  • Applications with complex data relationships
  • APIs that need to return different data to different clients
  • Projects aiming to optimize network usage
  • Microservices architecture integration

For example, you can use GraphQL to retrieve only the required fields from a product database, improving efficiency in e-commerce applications.

What Are Queries, Mutations, and Resolvers?

  • Query: Used to read or fetch data. In our case, we will create a query to retrieve products.
  • Mutation: Used to create, update, or delete data. We will create mutations to add new products to our datasource.
  • Resolvers: Functions that handle the execution of a query or mutation and interact with the datasource, such as a PostgreSQL database.

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 Implementation

Project Initialization

We will use Node.js and it already has a package manager called npm. Let's start by creating a new Node.js project:

mkdir pg-graphql
cd pg-graphql
npm init -y

Installing Dependencies

We need to install the following packages:

  • apollo-server: A GraphQL server library for Node.js.
  • graphql: The JavaScript reference implementation for GraphQL.
  • pg: A PostgreSQL client for Node.js.
  • datasource-sql: A SQL datasource for Apollo Server.
npm install apollo-server graphql pg datasource-sql

Creating Schema

Create a new file named schema.js in the root directory of your project and define the schema for your product API:

schema.js
const { gql } = require('apollo-server');

const typeDefs = gql`
type Product {
id: ID!
name: String!
price: Float!
description: String
}

type Query {
products: [Product]
product(id: ID!): Product
}

type Mutation {
createProduct(name: String!, price: Float!, description: String): Product
updateProduct(id: ID!, name: String, price: Float, description: String): Product
}
`;

module.exports = typeDefs;

Line 4: Defines the Product type with fields id, name, price, and description.

Line 11: Defines the Query type with two queries: products to fetch all products and product to fetch a single product by ID.

Line 16: Defines the Mutation type with two mutations: createProduct to add a new product and updateProduct to update an existing product.

This is kind of a contract of our API, and we will be using this in the server definition soon.

Implementing Product API

Create a new file named product_api.js in the root directory of your project and implement the product API:

product_api.js
const { SQLDataSource } = require('datasource-sql');

class ProductAPI extends SQLDataSource {
getProducts() {
return this.knex.select('*').from('products');
}

getProductById(id) {
return this.knex('products').where({ id }).first();
}

updateProduct(id, product) {
return this.knex('products')
.where({ id })
.update(product)
.returning('*')
.then(rows => rows[0]);
}

createProduct({ name, price, description }) {
return this.knex('products')
.insert({ name, price, description })
.returning('*');
}
}

module.exports = ProductAPI;

Knex is a SQL query builder for Node.js that we will use to interact with our PostgreSQL database.

Line 5: Retrieves all products from the products table.

Line 9: Retrieves a single product by ID.

Line 13: Updates an existing product by ID.

Line 21: Adds a new product to the products table.

Now that we have our API, let's use it in resolvers to handle queries and mutations.

Implementing Resolvers

Create a new file named resolvers.js in the root directory of your project and implement the resolvers:

resolvers.js
const resolvers = {
Query: {
products: async (_, __, { dataSources }) => {
return dataSources.productAPI.getProducts();
},
product: async (_, { id }, { dataSources }) => {
return dataSources.productAPI.getProductById(id);
},
},
Mutation: {
createProduct: async (_, { name, price, description }, { dataSources }) => {
return dataSources.productAPI.createProduct({ name, price, description });
},
updateProduct: async (_, { id, name, price, description }, { dataSources }) => {
const updatedProduct = { name, price, description };
return dataSources.productAPI.updateProduct(id, updatedProduct);
},
},
};

module.exports = resolvers;

Resolvers are functions that execute queries and mutations. They interact with the datasource to fetch or update data. In our case, it uses our Product API for better abstraction. With the help of resolvers, queries and mutations will be available in our GraphQL API.

Setting Up Apollo Server

Create a new file named server.js in the root directory of your project and set up the Apollo Server:

server.js
const { ApolloServer } = require('apollo-server');
const typeDefs = require('./schema');
const resolvers = require('./resolvers');
const ProductAPI = require('./product_api');

// PostgreSQL connection setup
const knexConfig = {
client: 'pg',
connection: {
host: '<host>',
user: '<user>',
password: '<password>',
database: '<database>',
ssl: {
rejectUnauthorized: false
},
application_name: 'apollo'
},
pool: { min: 0, max: 3 },
};

const server = new ApolloServer({
typeDefs,
resolvers,
dataSources: () => ({
productAPI: new ProductAPI(knexConfig),
}),
introspection: true,
});

server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
});

Line 22: ApolloServer is initialized with the schema, resolvers, and data sources. With the instrospection option set to true, you can explore the schema using tools like GraphQL Playground.

Running Application

Run the following command to start your Apollo Server:

node server.js

Testing the API

Once the server is ready, navigate to http://localhost:4000 in your browser to access the GraphQL Playground. You will see a button to go to the Apollo Studio page where you can explore your schema, run queries, and test mutations.

Creating a New Product

To create a new product, run the following mutation in the Playground:

mutation {
createProduct(name: "New Product", price: 99.99, description: "description") {
id
name
price
description
}
}

Fetching All Products

To fetch all products, run the following query:

query {
products {
id
name
price
description
}
}

Getting a Single Product

query {
product(id: 1) {
id
name
price
description
}
}

Updating a Product

mutation {
updateProduct(id: 1, name: "Updated Product", price: 199.99, description: "New Description") {
id
name
price
description
}
}

Conclusion

In this article, we covered how to set up a simple product API using Apollo GraphQL and PostgreSQL as a service. GraphQL’s flexibility with queries and mutations, combined with a powerful PostgreSQL datasource, makes it a great solution for building efficient APIs. If you're looking for a managed PostgreSQL solution, consider using RapidApp PostgreSQL to get started quickly.

tip

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