Introduction: Why Does Your Project Need an API Gateway?
\n\nImagine: you are writing a startup or enterprise application in TypeScript. You already have a user service, an order service, and a notification service. Each one lives on its own port (or even its own server), communicates via HTTP, and uses its own database. Everything works, but the client (frontend, mobile app) starts to struggle: to assemble a profile page, it needs to make 4-5 requests to different services, deal with different error formats, and also authenticate with each one.
\n\nThis is where the API Gateway comes into play — a single entry point for all clients. It handles routing, authentication, data aggregation, caching, and even response transformation. In this article, we will build a real API Gateway in TypeScript from scratch, examine patterns and anti-patterns, and show you how to avoid common pitfalls.
\n\nWhat is an API Gateway in Simple Terms
\n\nAn API Gateway is a layer between the client and your internal services. The client (browser, mobile app, external API) only communicates with the Gateway. The Gateway looks at the request path, checks the token, and redirects the request to the appropriate microservice. Sometimes it even aggregates responses from multiple services into one — this is called aggregation.
\n\nWhy is this needed in the TypeScript ecosystem? Because TypeScript gives us types, interfaces, and strictness, and the Gateway is the perfect place for validation and converting data to a unified contract.
\n\nArchitecture: How It Works in TypeScript
\n\nLet's design a simple but functional architecture. We will have:
\n\n- \n
- Gateway (our TypeScript service) — port 3000, the single entry point. \n
- User Service — port 4001, REST API. \n
- Order Service — port 4002, REST API. \n
- Notification Service — port 4003, REST API. \n
All internal services live in their own network (e.g., Docker network) and are not visible from the outside. The client only communicates with the Gateway.
\n\n// Project structure\ngateway/\n├── src/\n│ ├── routes/\n│ │ ├── userRoutes.ts\n│ │ ├── orderRoutes.ts\n│ │ └── notificationRoutes.ts\n│ ├── middleware/\n│ │ ├── authMiddleware.ts\n│ │ ├── rateLimiter.ts\n│ │ └── cacheMiddleware.ts\n│ ├── services/\n│ │ └── proxyService.ts\n│ ├── types/\n│ │ └── index.ts\n│ └── index.ts\n├── package.json\n└── tsconfig.json\n\n\nWriting a Basic API Gateway in TypeScript
\n\nLet's start by installing dependencies. We'll need Express (the most popular framework), http-proxy-middleware for proxying, and a few utilities.
\n\nnpm init -y\nnpm install express http-proxy-middleware cors helmet express-rate-limit\nnpm install -D typescript @types/node @types/express ts-node nodemon\n\n\nCreate tsconfig.json:
{\n "compilerOptions": {\n "target": "ES2020",\n "module": "commonjs",\n "outDir": "./dist",\n "rootDir": "./src",\n "strict": true,\n "esModuleInterop": true,\n "skipLibCheck": true,\n "forceConsistentCasingInFileNames": true\n },\n "include": ["src/**/*"],\n "exclude": ["node_modules"]\n}\n\n\nNow let's write the main file src/index.ts:
import express, { Request, Response } from 'express';\nimport cors from 'cors';\nimport helmet from 'helmet';\nimport { createProxyMiddleware } from 'http-proxy-middleware';\nimport { authMiddleware } from './middleware/authMiddleware';\nimport { rateLimiter } from './middleware/rateLimiter';\n\nconst app = express();\nconst PORT = process.env.PORT || 3000;\n\n// Global middleware\napp.use(helmet());\napp.use(cors());\napp.use(express.json());\napp.use(rateLimiter);\n\n// Proxy for user service\napp.use('/api/users', auth