DEV Community

Cover image for The Complete Guide to Building REST APIs
Akshay Kurve
Akshay Kurve

Posted on

The Complete Guide to Building REST APIs

The Complete Guide to Building REST APIs

If you've spent any time building modern web applications, you've probably interacted with a REST API.

When your frontend fetches user data, when a mobile app sends a login request, or when two services communicate - there's usually an API behind it.

But when you're starting out, REST APIs can feel confusing:

  • What exactly is REST?
  • How do endpoints work?
  • How do you actually build one?
  • What are best practices?

In this guide, we'll walk through everything you need to know about building REST APIs.

The goal is simple:

By the end of this article, you'll understand how REST APIs work and how to build one from scratch.

No unnecessary theory - just practical explanations and examples.


What is a REST API?

REST stands for Representational State Transfer.

That sounds complicated, but the idea is actually simple.

A REST API is a way for applications to communicate using HTTP requests.

Example:

GET /users
Enter fullscreen mode Exit fullscreen mode

This request asks the server:

"Give me the list of users."

The server responds with data, usually in JSON format:

[
  { "id": 1, "name": "Alex" },
  { "id": 2, "name": "Sarah" }
]
Enter fullscreen mode Exit fullscreen mode

Think of a REST API like a menu in a restaurant:

  • The menu → list of endpoints
  • The order → the request
  • The kitchen → the server
  • The food → the response

What is an Endpoint?

An endpoint is simply a URL where your API can be accessed.

Example endpoints:

GET /users
GET /users/1
POST /users
PUT /users/1
DELETE /users/1
Enter fullscreen mode Exit fullscreen mode

Each endpoint performs a different action.


HTTP Methods Explained

REST APIs rely heavily on HTTP methods (also called HTTP verbs).

GET

Used to retrieve data.

Example:

GET /users
Enter fullscreen mode Exit fullscreen mode

Returns all users.

Response:

[
  { "id": 1, "name": "Alex" },
  { "id": 2, "name": "Sarah" }
]
Enter fullscreen mode Exit fullscreen mode

POST

Used to create new data.

Example:

POST /users
Content-Type: application/json
Enter fullscreen mode Exit fullscreen mode

Request body:

{
  "name": "Alex",
  "email": "alex@example.com"
}
Enter fullscreen mode Exit fullscreen mode

Response:

{
  "id": 3,
  "name": "Alex",
  "email": "alex@example.com"
}
Enter fullscreen mode Exit fullscreen mode

PUT

Used to update existing data (full replacement).

Example:

PUT /users/1
Content-Type: application/json
Enter fullscreen mode Exit fullscreen mode

Request body:

{
  "name": "Alex Updated",
  "email": "alex.new@example.com"
}
Enter fullscreen mode Exit fullscreen mode

PATCH

Used to partially update existing data.

Example:

PATCH /users/1
Content-Type: application/json
Enter fullscreen mode Exit fullscreen mode

Request body:

{
  "email": "alex.new@example.com"
}
Enter fullscreen mode Exit fullscreen mode

DELETE

Used to remove data.

Example:

DELETE /users/1
Enter fullscreen mode Exit fullscreen mode

Response:

{
  "message": "User deleted successfully"
}
Enter fullscreen mode Exit fullscreen mode

REST API Design Principles

A well-designed REST API follows these principles:

1. Use Nouns, Not Verbs

Bad:

/getUsers
/createUser
/deleteUser
Enter fullscreen mode Exit fullscreen mode

Good:

GET /users
POST /users
DELETE /users/1
Enter fullscreen mode Exit fullscreen mode

2. Use Plural Nouns for Collections

Bad:

GET /user
Enter fullscreen mode Exit fullscreen mode

Good:

GET /users
Enter fullscreen mode Exit fullscreen mode

3. Use Resource Nesting for Relationships

Example:

GET /users/1/posts          # Get all posts by user 1
GET /users/1/posts/5        # Get post 5 by user 1
POST /users/1/posts         # Create a new post for user 1
Enter fullscreen mode Exit fullscreen mode

4. Use Query Parameters for Filtering and Sorting

Examples:

GET /users?role=admin
GET /users?sort=name&order=asc
GET /users?page=2&limit=10
Enter fullscreen mode Exit fullscreen mode

Setting Up a REST API with Express.js

Let's build a simple API using Express.js, one of the most popular Node.js frameworks.

Step 1: Initialize a Project

mkdir rest-api-demo
cd rest-api-demo
npm init -y
Enter fullscreen mode Exit fullscreen mode

Step 2: Install Express

npm install express
Enter fullscreen mode Exit fullscreen mode

Step 3: Create a Server

Create a file named server.js:

const express = require('express');
const app = express();

// Middleware to parse JSON
app.use(express.json());

// Start server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});
Enter fullscreen mode Exit fullscreen mode

Run the server:

node server.js
Enter fullscreen mode Exit fullscreen mode

Now you have a running server on http://localhost:3000.


Creating Your First Endpoint

Let's create a basic GET endpoint.

Add this to server.js:

// In-memory data store
let users = [
  { id: 1, name: 'Alex', email: 'alex@example.com' },
  { id: 2, name: 'Sarah', email: 'sarah@example.com' }
];

// GET all users
app.get('/users', (req, res) => {
  res.json(users);
});
Enter fullscreen mode Exit fullscreen mode

Test it:

Visit http://localhost:3000/users in your browser, or use:

curl http://localhost:3000/users
Enter fullscreen mode Exit fullscreen mode

Response:

[
  { "id": 1, "name": "Alex", "email": "alex@example.com" },
  { "id": 2, "name": "Sarah", "email": "sarah@example.com" }
]
Enter fullscreen mode Exit fullscreen mode

Getting a Single Resource

Add a GET endpoint for a specific user:

// GET single user by ID
app.get('/users/:id', (req, res) => {
  const id = parseInt(req.params.id);
  const user = users.find(u => u.id === id);

  if (!user) {
    return res.status(404).json({ 
      error: 'User not found' 
    });
  }

  res.json(user);
});
Enter fullscreen mode Exit fullscreen mode

Test it:

curl http://localhost:3000/users/1
Enter fullscreen mode Exit fullscreen mode

Creating Data with POST

Add a POST endpoint to create users:

// POST create new user
app.post('/users', (req, res) => {
  const { name, email } = req.body;

  // Basic validation
  if (!name || !email) {
    return res.status(400).json({ 
      error: 'Name and email are required' 
    });
  }

  const newUser = {
    id: users.length + 1,
    name,
    email
  };

  users.push(newUser);

  res.status(201).json(newUser);
});
Enter fullscreen mode Exit fullscreen mode

Test it:

curl -X POST http://localhost:3000/users \
  -H "Content-Type: application/json" \
  -d '{"name":"John","email":"john@example.com"}'
Enter fullscreen mode Exit fullscreen mode

Response:

{
  "id": 3,
  "name": "John",
  "email": "john@example.com"
}
Enter fullscreen mode Exit fullscreen mode

Updating Data with PUT

Add a PUT endpoint to update users:

// PUT update user
app.put('/users/:id', (req, res) => {
  const id = parseInt(req.params.id);
  const { name, email } = req.body;

  const userIndex = users.findIndex(u => u.id === id);

  if (userIndex === -1) {
    return res.status(404).json({ 
      error: 'User not found' 
    });
  }

  // Validate input
  if (!name || !email) {
    return res.status(400).json({ 
      error: 'Name and email are required' 
    });
  }

  users[userIndex] = { id, name, email };

  res.json(users[userIndex]);
});
Enter fullscreen mode Exit fullscreen mode

Test it:

curl -X PUT http://localhost:3000/users/1 \
  -H "Content-Type: application/json" \
  -d '{"name":"Alex Updated","email":"alex.new@example.com"}'
Enter fullscreen mode Exit fullscreen mode

Partial Updates with PATCH

Add a PATCH endpoint:

// PATCH partially update user
app.patch('/users/:id', (req, res) => {
  const id = parseInt(req.params.id);
  const user = users.find(u => u.id === id);

  if (!user) {
    return res.status(404).json({ 
      error: 'User not found' 
    });
  }

  // Update only provided fields
  if (req.body.name) user.name = req.body.name;
  if (req.body.email) user.email = req.body.email;

  res.json(user);
});
Enter fullscreen mode Exit fullscreen mode

Deleting Data

Add a DELETE endpoint:

// DELETE user
app.delete('/users/:id', (req, res) => {
  const id = parseInt(req.params.id);
  const userIndex = users.findIndex(u => u.id === id);

  if (userIndex === -1) {
    return res.status(404).json({ 
      error: 'User not found' 
    });
  }

  users.splice(userIndex, 1);

  res.status(204).send(); // No content
});
Enter fullscreen mode Exit fullscreen mode

Test it:

curl -X DELETE http://localhost:3000/users/1
Enter fullscreen mode Exit fullscreen mode

Building the Same API with Fastify

Fastify is a modern alternative to Express, designed for speed and efficiency.

Install Fastify

npm install fastify
Enter fullscreen mode Exit fullscreen mode

Create a Server

Create fastify-server.js:

const fastify = require('fastify')({ logger: true });

// In-memory data
let users = [
  { id: 1, name: 'Alex', email: 'alex@example.com' },
  { id: 2, name: 'Sarah', email: 'sarah@example.com' }
];

// GET all users
fastify.get('/users', async (request, reply) => {
  return users;
});

// GET single user
fastify.get('/users/:id', async (request, reply) => {
  const id = parseInt(request.params.id);
  const user = users.find(u => u.id === id);

  if (!user) {
    reply.code(404).send({ error: 'User not found' });
    return;
  }

  return user;
});

// POST create user
fastify.post('/users', async (request, reply) => {
  const { name, email } = request.body;

  if (!name || !email) {
    reply.code(400).send({ error: 'Name and email are required' });
    return;
  }

  const newUser = {
    id: users.length + 1,
    name,
    email
  };

  users.push(newUser);
  reply.code(201).send(newUser);
});

// Start server
const start = async () => {
  try {
    await fastify.listen({ port: 3000 });
  } catch (err) {
    fastify.log.error(err);
    process.exit(1);
  }
};

start();
Enter fullscreen mode Exit fullscreen mode

Fastify APIs are often 2-3x faster than Express while maintaining simplicity.


HTTP Status Codes Explained

Every API response should include an appropriate status code.

Common Status Codes

Code Meaning Use Case
200 OK Successful GET, PUT, PATCH
201 Created Successful POST
204 No Content Successful DELETE
400 Bad Request Invalid input data
401 Unauthorized Missing/invalid authentication
403 Forbidden Authenticated but not authorized
404 Not Found Resource doesn't exist
500 Server Error Unexpected server error

Example in Express:

res.status(201).json({
  message: 'User created successfully',
  user: newUser
});
Enter fullscreen mode Exit fullscreen mode

API Versioning

As your API evolves, you'll need versioning to avoid breaking existing clients.

URL Versioning (Recommended)

/api/v1/users
/api/v2/users
Enter fullscreen mode Exit fullscreen mode

Implementation:

const v1Router = express.Router();
const v2Router = express.Router();

v1Router.get('/users', (req, res) => {
  // Version 1 logic
});

v2Router.get('/users', (req, res) => {
  // Version 2 logic
});

app.use('/api/v1', v1Router);
app.use('/api/v2', v2Router);
Enter fullscreen mode Exit fullscreen mode

Header Versioning

GET /users
Accept: application/vnd.api.v1+json
Enter fullscreen mode Exit fullscreen mode

Input Validation

Never trust user input. Always validate and sanitize data.

Manual Validation

app.post('/users', (req, res) => {
  const { name, email } = req.body;

  if (!name || typeof name !== 'string' || name.trim().length === 0) {
    return res.status(400).json({ error: 'Valid name is required' });
  }

  if (!email || !email.includes('@')) {
    return res.status(400).json({ error: 'Valid email is required' });
  }

  // Process valid data
});
Enter fullscreen mode Exit fullscreen mode

Using Validation Libraries

With Joi:

npm install joi
Enter fullscreen mode Exit fullscreen mode
const Joi = require('joi');

const userSchema = Joi.object({
  name: Joi.string().min(2).max(50).required(),
  email: Joi.string().email().required()
});

app.post('/users', (req, res) => {
  const { error, value } = userSchema.validate(req.body);

  if (error) {
    return res.status(400).json({ 
      error: error.details[0].message 
    });
  }

  // Use validated data
  const newUser = { id: users.length + 1, ...value };
  users.push(newUser);
  res.status(201).json(newUser);
});
Enter fullscreen mode Exit fullscreen mode

With Zod:

npm install zod
Enter fullscreen mode Exit fullscreen mode
const { z } = require('zod');

const userSchema = z.object({
  name: z.string().min(2).max(50),
  email: z.string().email()
});

app.post('/users', (req, res) => {
  try {
    const validatedData = userSchema.parse(req.body);
    const newUser = { id: users.length + 1, ...validatedData };
    users.push(newUser);
    res.status(201).json(newUser);
  } catch (error) {
    res.status(400).json({ error: error.errors });
  }
});
Enter fullscreen mode Exit fullscreen mode

Error Handling

Proper error handling improves debugging and user experience.

Centralized Error Handler

// Error handling middleware (put this at the end)
app.use((err, req, res, next) => {
  console.error(err.stack);

  res.status(err.status || 500).json({
    error: {
      message: err.message || 'Internal server error',
      ...(process.env.NODE_ENV === 'development' && { stack: err.stack })
    }
  });
});
Enter fullscreen mode Exit fullscreen mode

Async Error Handling

// Wrapper for async route handlers
const asyncHandler = (fn) => (req, res, next) => {
  Promise.resolve(fn(req, res, next)).catch(next);
};

app.get('/users/:id', asyncHandler(async (req, res) => {
  const user = await getUserById(req.params.id);

  if (!user) {
    const error = new Error('User not found');
    error.status = 404;
    throw error;
  }

  res.json(user);
}));
Enter fullscreen mode Exit fullscreen mode

REST API Best Practices

1. Use Proper Status Codes

Don't return 200 OK for everything. Use appropriate codes:

res.status(201).json(newUser);  // Created
res.status(204).send();          // No content
res.status(404).json({ error }); // Not found
Enter fullscreen mode Exit fullscreen mode

2. Maintain Consistent Response Structure

Example format:

{
  "success": true,
  "data": {
    "id": 1,
    "name": "Alex"
  },
  "message": "User retrieved successfully"
}
Enter fullscreen mode Exit fullscreen mode

For errors:

{
  "success": false,
  "error": {
    "code": "USER_NOT_FOUND",
    "message": "User with ID 123 not found"
  }
}
Enter fullscreen mode Exit fullscreen mode

3. Implement Pagination

For large datasets, always paginate:

app.get('/users', (req, res) => {
  const page = parseInt(req.query.page) || 1;
  const limit = parseInt(req.query.limit) || 10;
  const startIndex = (page - 1) * limit;
  const endIndex = page * limit;

  const results = {
    total: users.length,
    page,
    limit,
    data: users.slice(startIndex, endIndex)
  };

  if (endIndex < users.length) {
    results.next = {
      page: page + 1,
      limit
    };
  }

  if (startIndex > 0) {
    results.previous = {
      page: page - 1,
      limit
    };
  }

  res.json(results);
});
Enter fullscreen mode Exit fullscreen mode

Usage:

GET /users?page=2&limit=10
Enter fullscreen mode Exit fullscreen mode

4. Implement Filtering and Sorting

app.get('/users', (req, res) => {
  let result = [...users];

  // Filter by role
  if (req.query.role) {
    result = result.filter(u => u.role === req.query.role);
  }

  // Sort
  if (req.query.sort) {
    const sortField = req.query.sort;
    const order = req.query.order === 'desc' ? -1 : 1;

    result.sort((a, b) => {
      if (a[sortField] < b[sortField]) return -1 * order;
      if (a[sortField] > b[sortField]) return 1 * order;
      return 0;
    });
  }

  res.json(result);
});
Enter fullscreen mode Exit fullscreen mode

Usage:

GET /users?role=admin&sort=name&order=asc
Enter fullscreen mode Exit fullscreen mode

5. Secure Your API

API Keys

const apiKeyMiddleware = (req, res, next) => {
  const apiKey = req.header('X-API-Key');

  if (!apiKey || apiKey !== process.env.API_KEY) {
    return res.status(401).json({ error: 'Invalid API key' });
  }

  next();
};

app.use('/api', apiKeyMiddleware);
Enter fullscreen mode Exit fullscreen mode

JWT Authentication

npm install jsonwebtoken
Enter fullscreen mode Exit fullscreen mode
const jwt = require('jsonwebtoken');

// Generate token
app.post('/login', (req, res) => {
  // Validate credentials...
  const token = jwt.sign(
    { userId: user.id },
    process.env.JWT_SECRET,
    { expiresIn: '1h' }
  );

  res.json({ token });
});

// Verify token
const authMiddleware = (req, res, next) => {
  const token = req.header('Authorization')?.replace('Bearer ', '');

  if (!token) {
    return res.status(401).json({ error: 'No token provided' });
  }

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.userId = decoded.userId;
    next();
  } catch (error) {
    res.status(401).json({ error: 'Invalid token' });
  }
};

app.get('/users', authMiddleware, (req, res) => {
  // Protected route
});
Enter fullscreen mode Exit fullscreen mode

6. Enable CORS

npm install cors
Enter fullscreen mode Exit fullscreen mode
const cors = require('cors');

app.use(cors({
  origin: 'https://your-frontend.com',
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization']
}));
Enter fullscreen mode Exit fullscreen mode

7. Rate Limiting

Prevent abuse by limiting requests:

npm install express-rate-limit
Enter fullscreen mode Exit fullscreen mode
const rateLimit = require('express-rate-limit');

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // Limit each IP to 100 requests per windowMs
  message: 'Too many requests, please try again later'
});

app.use('/api', limiter);
Enter fullscreen mode Exit fullscreen mode

8. Add Request Logging

npm install morgan
Enter fullscreen mode Exit fullscreen mode
const morgan = require('morgan');

app.use(morgan('combined'));
Enter fullscreen mode Exit fullscreen mode

Testing Your API

Manual Testing Tools

  1. Postman - GUI for testing APIs
  2. Insomnia - Alternative to Postman
  3. cURL - Command-line tool

cURL example:

# GET request
curl http://localhost:3000/users

# POST request
curl -X POST http://localhost:3000/users \
  -H "Content-Type: application/json" \
  -d '{"name":"John","email":"john@example.com"}'

# With authentication
curl http://localhost:3000/users \
  -H "Authorization: Bearer YOUR_TOKEN"
Enter fullscreen mode Exit fullscreen mode

Automated Testing

Install testing libraries:

npm install --save-dev jest supertest
Enter fullscreen mode Exit fullscreen mode

Create users.test.js:

const request = require('supertest');
const app = require('./server'); // Export your app

describe('User API', () => {
  test('GET /users should return all users', async () => {
    const response = await request(app).get('/users');

    expect(response.status).toBe(200);
    expect(Array.isArray(response.body)).toBe(true);
  });

  test('POST /users should create a new user', async () => {
    const newUser = {
      name: 'Test User',
      email: 'test@example.com'
    };

    const response = await request(app)
      .post('/users')
      .send(newUser);

    expect(response.status).toBe(201);
    expect(response.body).toHaveProperty('id');
    expect(response.body.name).toBe(newUser.name);
  });

  test('GET /users/:id should return 404 for non-existent user', async () => {
    const response = await request(app).get('/users/999');

    expect(response.status).toBe(404);
  });
});
Enter fullscreen mode Exit fullscreen mode

Run tests:

npx jest
Enter fullscreen mode Exit fullscreen mode

Complete Example: Full REST API

Here's a complete, production-ready example with all best practices:

const express = require('express');
const cors = require('cors');
const morgan = require('morgan');
const rateLimit = require('express-rate-limit');
const { z } = require('zod');

const app = express();

// Middleware
app.use(express.json());
app.use(cors());
app.use(morgan('combined'));

// Rate limiting
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 100
});
app.use('/api', limiter);

// In-memory database
let users = [
  { id: 1, name: 'Alex', email: 'alex@example.com', role: 'admin' },
  { id: 2, name: 'Sarah', email: 'sarah@example.com', role: 'user' }
];

// Validation schema
const userSchema = z.object({
  name: z.string().min(2).max(50),
  email: z.string().email(),
  role: z.enum(['user', 'admin']).optional()
});

// Async handler wrapper
const asyncHandler = (fn) => (req, res, next) => {
  Promise.resolve(fn(req, res, next)).catch(next);
};

// Routes
// GET all users with filtering, sorting, pagination
app.get('/api/v1/users', asyncHandler(async (req, res) => {
  let result = [...users];

  // Filter
  if (req.query.role) {
    result = result.filter(u => u.role === req.query.role);
  }

  // Sort
  if (req.query.sort) {
    const sortField = req.query.sort;
    const order = req.query.order === 'desc' ? -1 : 1;
    result.sort((a, b) => {
      if (a[sortField] < b[sortField]) return -1 * order;
      if (a[sortField] > b[sortField]) return 1 * order;
      return 0;
    });
  }

  // Paginate
  const page = parseInt(req.query.page) || 1;
  const limit = parseInt(req.query.limit) || 10;
  const startIndex = (page - 1) * limit;
  const endIndex = page * limit;

  const paginatedResults = {
    success: true,
    data: result.slice(startIndex, endIndex),
    pagination: {
      total: result.length,
      page,
      limit,
      totalPages: Math.ceil(result.length / limit)
    }
  };

  res.json(paginatedResults);
}));

// GET single user
app.get('/api/v1/users/:id', asyncHandler(async (req, res) => {
  const id = parseInt(req.params.id);
  const user = users.find(u => u.id === id);

  if (!user) {
    return res.status(404).json({
      success: false,
      error: { message: 'User not found' }
    });
  }

  res.json({
    success: true,
    data: user
  });
}));

// POST create user
app.post('/api/v1/users', asyncHandler(async (req, res) => {
  const validatedData = userSchema.parse(req.body);

  const newUser = {
    id: users.length + 1,
    ...validatedData,
    role: validatedData.role || 'user'
  };

  users.push(newUser);

  res.status(201).json({
    success: true,
    data: newUser,
    message: 'User created successfully'
  });
}));

// PUT update user
app.put('/api/v1/users/:id', asyncHandler(async (req, res) => {
  const id = parseInt(req.params.id);
  const userIndex = users.findIndex(u => u.id === id);

  if (userIndex === -1) {
    return res.status(404).json({
      success: false,
      error: { message: 'User not found' }
    });
  }

  const validatedData = userSchema.parse(req.body);
  users[userIndex] = { id, ...validatedData };

  res.json({
    success: true,
    data: users[userIndex],
    message: 'User updated successfully'
  });
}));

// PATCH partial update
app.patch('/api/v1/users/:id', asyncHandler(async (req, res) => {
  const id = parseInt(req.params.id);
  const user = users.find(u => u.id === id);

  if (!user) {
    return res.status(404).json({
      success: false,
      error: { message: 'User not found' }
    });
  }

  const partialSchema = userSchema.partial();
  const validatedData = partialSchema.parse(req.body);

  Object.assign(user, validatedData);

  res.json({
    success: true,
    data: user,
    message: 'User updated successfully'
  });
}));

// DELETE user
app.delete('/api/v1/users/:id', asyncHandler(async (req, res) => {
  const id = parseInt(req.params.id);
  const userIndex = users.findIndex(u => u.id === id);

  if (userIndex === -1) {
    return res.status(404).json({
      success: false,
      error: { message: 'User not found' }
    });
  }

  users.splice(userIndex, 1);

  res.status(204).send();
}));

// Error handling middleware
app.use((err, req, res, next) => {
  if (err instanceof z.ZodError) {
    return res.status(400).json({
      success: false,
      error: {
        message: 'Validation error',
        details: err.errors
      }
    });
  }

  console.error(err.stack);
  res.status(500).json({
    success: false,
    error: { message: 'Internal server error' }
  });
});

// 404 handler
app.use((req, res) => {
  res.status(404).json({
    success: false,
    error: { message: 'Route not found' }
  });
});

// Start server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

module.exports = app; // For testing
Enter fullscreen mode Exit fullscreen mode

Documentation

Always document your API. Popular tools include:

Swagger/OpenAPI

npm install swagger-jsdoc swagger-ui-express
Enter fullscreen mode Exit fullscreen mode
const swaggerJsdoc = require('swagger-jsdoc');
const swaggerUi = require('swagger-ui-express');

const swaggerOptions = {
  definition: {
    openapi: '3.0.0',
    info: {
      title: 'Users API',
      version: '1.0.0',
      description: 'A simple users API'
    },
    servers: [
      {
        url: 'http://localhost:3000/api/v1'
      }
    ]
  },
  apis: ['./server.js']
};

const swaggerSpec = swaggerJsdoc(swaggerOptions);
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));
Enter fullscreen mode Exit fullscreen mode

Visit http://localhost:3000/api-docs to see interactive documentation.


Deployment Checklist

Before deploying to production:

  • [ ] Use environment variables for sensitive data
  • [ ] Enable HTTPS
  • [ ] Set up proper logging
  • [ ] Implement rate limiting
  • [ ] Add authentication/authorization
  • [ ] Validate all inputs
  • [ ] Handle errors gracefully
  • [ ] Add request/response compression
  • [ ] Set security headers (use helmet)
  • [ ] Monitor API performance
  • [ ] Set up automated backups (if using a database)
  • [ ] Document your API
  • [ ] Write tests

My Thoughts On This

Building REST APIs is one of the most essential skills for modern developers.

Once you understand the fundamentals - HTTP methods, endpoints, status codes, and JSON responses - everything becomes much clearer.

Start small:

  1. Build a simple CRUD API (users, posts, or todos)
  2. Add a database (PostgreSQL, MongoDB, etc.)
  3. Implement authentication (JWT or OAuth)
  4. Add input validation and error handling
  5. Deploy to production (Heroku, Railway, AWS, etc.)

Before long, you'll be building production-ready APIs that power real applications.


What's Next?

Now that you understand REST APIs, consider exploring:

  • GraphQL - An alternative to REST
  • WebSockets - For real-time communication
  • gRPC - For high-performance APIs
  • API Gateways - For managing multiple services
  • Microservices Architecture - Breaking apps into smaller services

Additional Resources


Top comments (0)