Learn to build professional REST APIs with proper documentation, testing, and best practices using Node.js and Express.js.
Learn to build professional REST APIs with proper documentation, testing, and best practices using Node.js and Express.js.
Learn RESTful API design principles and best practices for building scalable APIs
Content by: Sachin Patel
Node.js Developer
REST (Representational State Transfer) is an architectural style for designing networked applications. RESTful APIs use HTTP methods to perform CRUD operations on resources and follow specific conventions for URL structure and response formats.
// RESTful API structure
const express = require('express');
const router = express.Router();
// GET /api/users - Get all users
router.get('/users', async (req, res) => {
try {
const users = await User.find();
res.json({
success: true,
data: users,
count: users.length
});
} catch (error) {
res.status(500).json({
success: false,
error: 'Failed to fetch users'
});
}
});
// GET /api/users/:id - Get user by ID
router.get('/users/:id', async (req, res) => {
try {
const user = await User.findById(req.params.id);
if (!user) {
return res.status(404).json({
success: false,
error: 'User not found'
});
}
res.json({
success: true,
data: user
});
} catch (error) {
res.status(500).json({
success: false,
error: 'Failed to fetch user'
});
}
});
// POST /api/users - Create new user
router.post('/users', async (req, res) => {
try {
const user = new User(req.body);
const savedUser = await user.save();
res.status(201).json({
success: true,
data: savedUser
});
} catch (error) {
res.status(400).json({
success: false,
error: 'Failed to create user'
});
}
});
// PUT /api/users/:id - Update user
router.put('/users/:id', async (req, res) => {
try {
const user = await User.findByIdAndUpdate(
req.params.id,
req.body,
{ new: true, runValidators: true }
);
if (!user) {
return res.status(404).json({
success: false,
error: 'User not found'
});
}
res.json({
success: true,
data: user
});
} catch (error) {
res.status(400).json({
success: false,
error: 'Failed to update user'
});
}
});
// DELETE /api/users/:id - Delete user
router.delete('/users/:id', async (req, res) => {
try {
const user = await User.findByIdAndDelete(req.params.id);
if (!user) {
return res.status(404).json({
success: false,
error: 'User not found'
});
}
res.json({
success: true,
message: 'User deleted successfully'
});
} catch (error) {
res.status(500).json({
success: false,
error: 'Failed to delete user'
});
}
});
// Query parameters and pagination
router.get('/users', async (req, res) => {
try {
const { page = 1, limit = 10, sort = 'createdAt', order = 'desc' } = req.query;
const skip = (page - 1) * limit;
const sortOrder = order === 'desc' ? -1 : 1;
const users = await User.find()
.sort({ [sort]: sortOrder })
.limit(parseInt(limit))
.skip(skip);
const total = await User.countDocuments();
res.json({
success: true,
data: users,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total,
pages: Math.ceil(total / limit)
}
});
} catch (error) {
res.status(500).json({
success: false,
error: 'Failed to fetch users'
});
}
});
Test your understanding of this topic:
Learn to create comprehensive API documentation using Swagger/OpenAPI
Content by: Parth Patel
Node.js Developer
API documentation is essential for developers who will use your API. Swagger/OpenAPI provides a standardized way to document REST APIs with interactive documentation.
// Install dependencies
npm install swagger-jsdoc swagger-ui-express
const swaggerJsdoc = require('swagger-jsdoc');
const swaggerUi = require('swagger-ui-express');
// Swagger configuration
const swaggerOptions = {
definition: {
openapi: '3.0.0',
info: {
title: 'User Management API',
version: '1.0.0',
description: 'A RESTful API for managing users',
contact: {
name: 'API Support',
email: 'support@example.com'
}
},
servers: [
{
url: 'http://localhost:3000',
description: 'Development server'
}
],
components: {
securitySchemes: {
bearerAuth: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT'
}
}
}
},
apis: ['./routes/*.js']
};
const specs = swaggerJsdoc(swaggerOptions);
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs));
// API documentation with JSDoc comments
/**
* @swagger
* components:
* schemas:
* User:
* type: object
* required:
* - name
* - email
* properties:
* _id:
* type: string
* description: Auto-generated user ID
* name:
* type: string
* description: User's full name
* email:
* type: string
* format: email
* description: User's email address
* age:
* type: integer
* minimum: 0
* maximum: 120
* description: User's age
* createdAt:
* type: string
* format: date-time
* description: User creation timestamp
*/
/**
* @swagger
* /api/users:
* get:
* summary: Get all users
* description: Retrieve a list of all users with pagination
* tags: [Users]
* parameters:
* - in: query
* name: page
* schema:
* type: integer
* default: 1
* description: Page number
* - in: query
* name: limit
* schema:
* type: integer
* default: 10
* description: Number of users per page
* responses:
* 200:
* description: List of users
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: array
* items:
* $ref: '#/components/schemas/User'
* pagination:
* type: object
* properties:
* page:
* type: integer
* limit:
* type: integer
* total:
* type: integer
* pages:
* type: integer
* 500:
* description: Server error
*/
/**
* @swagger
* /api/users:
* post:
* summary: Create a new user
* description: Create a new user with the provided information
* tags: [Users]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - name
* - email
* properties:
* name:
* type: string
* description: User's full name
* email:
* type: string
* format: email
* description: User's email address
* age:
* type: integer
* minimum: 0
* maximum: 120
* description: User's age
* responses:
* 201:
* description: User created successfully
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* $ref: '#/components/schemas/User'
* 400:
* description: Bad request
* 500:
* description: Server error
*/
Test your understanding of this topic:
Learn to test APIs using Jest, Supertest, and other testing tools
Content by: Bansi Patel
Node.js Developer
API testing is crucial for ensuring your endpoints work correctly and handle various scenarios properly. Jest and Supertest are popular tools for testing Node.js APIs.
// Install testing dependencies
npm install --save-dev jest supertest
// test/users.test.js
const request = require('supertest');
const app = require('../app');
const mongoose = require('mongoose');
const User = require('../models/User');
describe('User API', () => {
beforeAll(async () => {
// Connect to test database
await mongoose.connect(process.env.MONGODB_TEST_URI);
});
afterAll(async () => {
// Clean up and disconnect
await mongoose.connection.dropDatabase();
await mongoose.connection.close();
});
beforeEach(async () => {
// Clear users collection before each test
await User.deleteMany({});
});
describe('GET /api/users', () => {
it('should return empty array when no users exist', async () => {
const response = await request(app)
.get('/api/users')
.expect(200);
expect(response.body.success).toBe(true);
expect(response.body.data).toEqual([]);
expect(response.body.count).toBe(0);
});
it('should return all users', async () => {
// Create test users
const user1 = await User.create({
name: 'John Doe',
email: 'john@example.com',
age: 30
});
const user2 = await User.create({
name: 'Jane Smith',
email: 'jane@example.com',
age: 25
});
const response = await request(app)
.get('/api/users')
.expect(200);
expect(response.body.success).toBe(true);
expect(response.body.data).toHaveLength(2);
expect(response.body.count).toBe(2);
});
it('should support pagination', async () => {
// Create multiple users
const users = [];
for (let i = 0; i < 15; i++) {
users.push({
name: `User ${i}`,
email: `user${i}@example.com`,
age: 20 + i
});
}
await User.insertMany(users);
const response = await request(app)
.get('/api/users?page=1&limit=10')
.expect(200);
expect(response.body.data).toHaveLength(10);
expect(response.body.pagination.page).toBe(1);
expect(response.body.pagination.limit).toBe(10);
expect(response.body.pagination.total).toBe(15);
expect(response.body.pagination.pages).toBe(2);
});
});
describe('POST /api/users', () => {
it('should create a new user', async () => {
const userData = {
name: 'John Doe',
email: 'john@example.com',
age: 30
};
const response = await request(app)
.post('/api/users')
.send(userData)
.expect(201);
expect(response.body.success).toBe(true);
expect(response.body.data.name).toBe(userData.name);
expect(response.body.data.email).toBe(userData.email);
expect(response.body.data.age).toBe(userData.age);
});
it('should return 400 for invalid data', async () => {
const invalidData = {
name: '', // Empty name
email: 'invalid-email', // Invalid email
age: -5 // Invalid age
};
const response = await request(app)
.post('/api/users')
.send(invalidData)
.expect(400);
expect(response.body.success).toBe(false);
});
});
describe('GET /api/users/:id', () => {
it('should return user by ID', async () => {
const user = await User.create({
name: 'John Doe',
email: 'john@example.com',
age: 30
});
const response = await request(app)
.get(`/api/users/${user._id}`)
.expect(200);
expect(response.body.success).toBe(true);
expect(response.body.data._id).toBe(user._id.toString());
});
it('should return 404 for non-existent user', async () => {
const fakeId = new mongoose.Types.ObjectId();
const response = await request(app)
.get(`/api/users/${fakeId}`)
.expect(404);
expect(response.body.success).toBe(false);
expect(response.body.error).toBe('User not found');
});
});
describe('PUT /api/users/:id', () => {
it('should update user', async () => {
const user = await User.create({
name: 'John Doe',
email: 'john@example.com',
age: 30
});
const updateData = {
name: 'John Updated',
age: 31
};
const response = await request(app)
.put(`/api/users/${user._id}`)
.send(updateData)
.expect(200);
expect(response.body.success).toBe(true);
expect(response.body.data.name).toBe(updateData.name);
expect(response.body.data.age).toBe(updateData.age);
});
});
describe('DELETE /api/users/:id', () => {
it('should delete user', async () => {
const user = await User.create({
name: 'John Doe',
email: 'john@example.com',
age: 30
});
const response = await request(app)
.delete(`/api/users/${user._id}`)
.expect(200);
expect(response.body.success).toBe(true);
expect(response.body.message).toBe('User deleted successfully');
// Verify user is deleted
const deletedUser = await User.findById(user._id);
expect(deletedUser).toBeNull();
});
});
});
// jest.config.js
module.exports = {
testEnvironment: 'node',
testMatch: ['**/__tests__/**/*.js', '**/?(*.)+(spec|test).js'],
collectCoverageFrom: [
'routes/**/*.js',
'models/**/*.js',
'middleware/**/*.js'
],
coverageDirectory: 'coverage',
coverageReporters: ['text', 'lcov', 'html']
};
Test your understanding of this topic:
Continue your learning journey and master the next set of concepts.
Continue to Module 6