Building Scalable APIs with Node.js
Best practices for creating robust and scalable REST APIs using Node.js, Express, and modern architectural patterns.

Building Scalable APIs with Node.js
Learn how to build production-ready REST APIs with modern best practices using Node.js and PostgreSQL.
Architecture Overview
A well-structured API follows these principles:
- RESTful design β consistent resource naming and HTTP methods
- Proper error handling β meaningful error responses with appropriate status codes
- Authentication & Authorization β secure endpoints with JWT or session-based auth
- Rate limiting β protect against abuse and DDoS attacks
- Database optimization β efficient queries with proper indexing
Setting Up Express
import express from 'express'
import { Pool } from 'pg'
import cors from 'cors'
import helmet from 'helmet'
const app = express()
const pool = new Pool({
connectionString: process.env.DATABASE_URL
})
// Middleware
app.use(helmet())
app.use(cors())
app.use(express.json())
// Health check
app.get('/health', (req, res) => {
res.json({ status: 'healthy', timestamp: new Date().toISOString() })
})
app.listen(3000, () => {
console.log('Server running on port 3000')
})
Database Connection Pooling
Connection pooling is critical for scalable APIs. The pg library provides built-in connection pooling:
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
max: 20, // Maximum pool size
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
})
Input Validation
Always validate incoming data using libraries like Zod:
import { z } from 'zod'
const createUserSchema = z.object({
email: z.string().email(),
name: z.string().min(2).max(100),
password: z.string().min(8),
})
app.post('/api/users', async (req, res) => {
const result = createUserSchema.safeParse(req.body)
if (!result.success) {
return res.status(400).json({ errors: result.error.issues })
}
// Process valid data...
})
Error Handling
Create a centralized error handler:
class AppError extends Error {
constructor(
public statusCode: number,
public message: string,
public isOperational = true
) {
super(message)
}
}
app.use((err, req, res, next) => {
const statusCode = err.statusCode || 500
res.status(statusCode).json({
error: err.message,
...(process.env.NODE_ENV === 'development' && { stack: err.stack }),
})
})
Rate Limiting
Protect your API with rate limiting:
import rateLimit from 'express-rate-limit'
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per window
})
app.use('/api/', limiter)
Conclusion
Building scalable APIs requires careful planning and following best practices. By implementing proper connection pooling, validation, error handling, and rate limiting, you'll create robust APIs that can handle production workloads.
Share this article
Subscribe to our newsletter
Get the latest articles, tutorials, and insights delivered straight to your inbox. No spam, unsubscribe anytime.
Related Articles

Modern CSS Techniques for 2024
Explore the latest CSS features including container queries, cascade layers, and advanced grid layouts.
Read more
Getting Started with Next.js 15
Learn about the latest features in Next.js 15 including async components, improved performance, and new routing capabilities.
Read more
Authentication Best Practices
Implement secure authentication with JWT, OAuth, and modern security patterns for web applications.
Read more