May 30, 2025 · 20 Min read · Beginner

Building a REST API with Node.js, Express, and TypeScript: A Comprehensive Guide

What You'll Learn

Master the fundamentals of REST API development with modern tools and best practices.

Project setup & configuration
Express routing & middleware
TypeScript best practices
Error handling & validation
Building a REST API with Node.js, Express, and TypeScript

Welcome to this comprehensive guide on building REST APIs using Node.js, Express, and TypeScript! Whether you're new to backend development or looking to solidify your understanding of these technologies, this tutorial will walk you through the process step by step.

1. Introduction

What is a REST API?

A REST (Representational State Transfer) API is an architectural style for designing networked applications. It relies on a stateless, client-server communication protocol, almost always HTTP. REST APIs are widely used for web services, allowing different systems to communicate with each other over the internet.

Why Node.js, Express, and TypeScript?

Node.js

JavaScript runtime built on Chrome's V8 engine. Excellent performance for I/O-bound applications.

Express

Minimal and flexible Node.js framework with robust features for web and mobile applications.

TypeScript

JavaScript superset with static typing. Catches errors early and improves code maintainability.

In this tutorial, we'll cover everything from setting up your project to implementing error handling and discussing best practices.

2. Prerequisites

Before we start, ensure you have the following installed:

Node.js and npm:

Download from nodejs.org. npm comes bundled with Node.js.

Code Editor:

Visual Studio Code is highly recommended for TypeScript development.

JavaScript/TypeScript:

Basic understanding of core concepts will be helpful.

Command Line:

Familiarity with terminal/command line operations.

3. Setting Up the Project

Let's get our project environment ready.

1 Initialize Node.js Project

Create a new directory for your project and navigate into it using your terminal:

Terminal
mkdir my-rest-api
cd my-rest-api
npm init -y

This creates a package.json file with default settings.

2 Install Dependencies

We need Express for the server, TypeScript for static typing, and a few development dependencies:

Terminal
npm install express
                                  npm install --save-dev typescript @types/express @types/node ts-node nodemon
                              

Production Dependencies

  • express - The web framework

Development Dependencies

  • typescript - TypeScript compiler
  • @types/* - Type definitions
  • ts-node - Run TypeScript directly
  • nodemon - Auto-restart on changes

3 Configure TypeScript

Create a TypeScript configuration file (tsconfig.json) in the root of your project:

Terminal
npx tsc --init

This command generates a tsconfig.json file. Open it and modify/uncomment these essential options:

tsconfig.json
{
  "compilerOptions": {
                                "target": "ES2020",
    "module": "commonjs",
    "rootDir": "./src",
    "outDir": "./dist",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true
  },
                                "include": ["src/**/*"],
  "exclude": ["node_modules"]
}
target:

ECMAScript target version

module:

Module code generation

rootDir:

Root directory of source files

outDir:

Output directory for compiled JS

strict:

Enable strict type-checking

esModuleInterop:

CommonJS/ES module compatibility

4 Project Structure

Create a src directory in your project root. This is where our TypeScript code will live.

Terminal
mkdir src
                                touch src/index.ts

Project Structure

my-rest-api/
├── node_modules/
├── src/
│   └── index.ts
├── package.json
├── package-lock.json
└── tsconfig.json

5 Add Scripts to package.json

Open your package.json and add the following scripts:

package.json
{
                                "main": "dist/index.js",
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js",
    "dev": "nodemon src/index.ts",
    "test": "echo \"Error: no test specified\" && exit 1"
  }
}
build

Compiles TypeScript to JavaScript

start

Runs compiled JavaScript (production)

dev

Development with auto-restart

4. Creating Your First Express Server

Let's write a simple server in src/index.ts:

src/index.ts
import express, { Request, Response, Application } from 'express';

const app: Application = express();
const port: number = process.env.PORT ? parseInt(process.env.PORT) : 3000;

app.get('/', (req: Request, res: Response) => {
  res.send('Hello, TypeScript with Express!');
});

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

Code Explanation

Import statements: We import express and its types (Request, Response, Application)

Application instance: const app: Application = express() creates our Express app

Port configuration: Uses environment variable or defaults to 3000

Route definition: app.get('/', ...) handles GET requests to the root path

Server start: app.listen(port, ...) starts the server

Run Your Server

To run this server in development mode:

Terminal
npm run dev
Open your browser and navigate to http://localhost:3000

5. Understanding Routing

Routing refers to how an application's endpoints (URIs) respond to client requests. Each route can have one or more handler functions, which are executed when the route is matched.

1 Basic Routes

Express supports common HTTP methods like GET, POST, PUT, DELETE:

src/index.ts
// Add these before app.listen()
app.post('/users', (req: Request, res: Response) => {
  res.status(201).send('User created');
});

app.put('/users/:id', (req: Request, res: Response) => {
  const userId = req.params.id;
  res.send(`User with ID ${userId} updated`);
});

app.delete('/users/:id', (req: Request, res: Response) => {
  const userId = req.params.id;
  res.send(`User with ID ${userId} deleted`);
});

2 Using express.Router

For larger applications, organize routes into separate files using express.Router:

Terminal
mkdir src/routes
touch src/routes/userRoutes.ts
src/routes/userRoutes.ts
import express, { Router, Request, Response } from 'express';

const router: Router = express.Router();

// Get all users
router.get('/', (req: Request, res: Response) => {
  res.json([{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]);
});

// Get a single user by ID
router.get('/:id', (req: Request, res: Response) => {
  const userId = req.params.id;
  res.json({ id: parseInt(userId), name: `User ${userId}` });
});

// Create a new user
router.post('/', (req: Request, res: Response) => {
  res.status(201).json({ message: 'User created successfully', user: req.body });
});

export default router;

6. Middleware

What is Middleware?

Middleware functions have access to the request object (req), response object (res), and the next middleware function (next) in the request-response cycle.

Logger Middleware Example
import { NextFunction } from 'express';

const loggerMiddleware = (req: Request, res: Response, next: NextFunction) => {
  console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
  next(); // Pass control to the next middleware
};

app.use(express.json()); // Parse JSON bodies
app.use(loggerMiddleware); // Apply logger to all requests

7. Error Handling

Error Handling Middleware
// Custom Error interface
interface AppError extends Error {
  statusCode?: number;
}

// Error handling middleware (add before app.listen())
app.use((err: AppError, req: Request, res: Response, next: NextFunction) => {
  console.error(err.stack);
  
  const statusCode = err.statusCode || 500;
  const message = err.message || 'Internal Server Error';

  res.status(statusCode).json({
    status: 'error',
    statusCode,
    message,
  });
});

8. Best Practices

Project Structure

  • • Separate routes, controllers, services
  • • Use consistent naming conventions
  • • Organize by feature/domain

Security

  • • Use helmet for security headers
  • • Validate and sanitize inputs
  • • Implement rate limiting

Code Quality

  • • Use ESLint and Prettier
  • • Write comprehensive tests
  • • Add clear documentation

Performance

  • • Use async/await properly
  • • Implement caching strategies
  • • Monitor and optimize queries

9. Conclusion

Congratulations!

You've learned the fundamentals of building REST APIs with Node.js, Express, and TypeScript!

What's Next?

Database integration (PostgreSQL, MongoDB)
Authentication & authorization (JWT, OAuth)
Testing with Jest and Supertest
Performance optimization
Containerization with Docker
Cloud deployment strategies
API versioning and documentation
Monitoring and logging

Building robust APIs takes practice and continuous learning. Keep exploring, building, and refining your skills!

Happy Coding!