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.
Table of Contents

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:
Download from nodejs.org. npm comes bundled with Node.js.
Visual Studio Code is highly recommended for TypeScript development.
Basic understanding of core concepts will be helpful.
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:
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:
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 definitionsts-node
- Run TypeScript directlynodemon
- Auto-restart on changes
3 Configure TypeScript
Create a TypeScript configuration file (tsconfig.json
) in the root
of your project:
npx tsc --init
This command generates a tsconfig.json
file. Open
it and modify/uncomment these essential options:
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"rootDir": "./src",
"outDir": "./dist",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
ECMAScript target version
Module code generation
Root directory of source files
Output directory for compiled JS
Enable strict type-checking
CommonJS/ES module compatibility
4 Project Structure
Create a src
directory in your
project root. This is where our TypeScript code will live.
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:
{
"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
:
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:
npm run dev
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:
// 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
:
mkdir src/routes
touch 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.
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
// 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?
Building robust APIs takes practice and continuous learning. Keep exploring, building, and refining your skills!