How to Use TypeScript & Docker to Build a REST Service with Debugging
TypeScript and Docker are a powerful combination for building reliable, maintainable REST services. TypeScript brings static typing and modern JavaScript features to your Node.js backend, while Docker ensures your service runs consistently across development, staging, and production environments. This guide explains how to wire the two together, including setting up remote debugging from IDEs such as WebStorm and Visual Studio Code.
Why Combine TypeScript and Docker for REST Services?
Running a TypeScript REST service inside Docker solves several common DevOps pain points:
- Reproducible builds: The TypeScript compilation step is baked into the Docker image, so every environment uses exactly the same compiled output.
- Dependency isolation: Node modules are installed inside the container, eliminating “works on my machine” problems caused by differing local
node_modulestrees. - Type safety at build time: TypeScript catches type errors during the Docker build step, preventing broken images from being deployed.
- Scalability: Containerised services are straightforward to orchestrate with Docker Compose, Kubernetes, or any container platform.
Project Structure
A typical TypeScript REST service prepared for Docker looks like this:
my-rest-service/
├── src/
│ ├── index.ts # Entry point
│ └── routes/
│ └── health.ts # Example route
├── dist/ # Compiled JS output (generated)
├── tsconfig.json
├── package.json
├── Dockerfile
└── docker-compose.ymlCode language: PHP (php)
TypeScript Configuration
A minimal tsconfig.json suitable for a Node.js REST service:
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"sourceMap": true
},
"include": ["src"]
}Code language: JSON / JSON with Comments (json)
Setting "sourceMap": true is essential for debugging inside Docker — it allows the debugger to map minified or compiled JavaScript back to the original TypeScript source files.
Writing a Simple REST Entry Point
The following example uses Express, one of the most common Node.js HTTP frameworks:
// src/index.ts
import express, { Request, Response } from 'express';
const app = express();
const PORT = process.env.PORT || 3000;
app.use(express.json());
app.get('/health', (_req: Request, res: Response) => {
res.json({ status: 'ok' });
});
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});Code language: JavaScript (javascript)
Dockerfile
A multi-stage Dockerfile keeps the final image small by discarding the TypeScript compiler and development dependencies before producing the production image:
# Stage 1 – build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY tsconfig.json ./
COPY src ./src
RUN npm run build
# Stage 2 – production image
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=builder /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/index.js"]Code language: PHP (php)
Ensure your package.json includes a build script that invokes the TypeScript compiler:
{
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "ts-node src/index.ts"
}
}Code language: JSON / JSON with Comments (json)
Docker Compose for Local Development
During development you typically want live reloading and a debug port exposed. A docker-compose.yml configured for this looks like:
version: '3.8'
services:
api:
build:
context: .
target: builder # Use the build stage so devDeps are available
volumes:
- ./src:/app/src # Mount source for live edits
ports:
- "3000:3000" # HTTP
- "9229:9229" # Node.js inspector (debugger)
command: node --inspect=0.0.0.0:9229 dist/index.js
environment:
- NODE_ENV=developmentCode language: PHP (php)
Port 9229 is the standard Node.js V8 inspector port. Exposing it from the container is all that is needed to attach a remote debugger.
Enabling Debugging in WebStorm
To attach WebStorm to a running container:
- Go to Run → Edit Configurations and add a new Node.js Remote Debug configuration.
- Set Host to
localhostand Port to9229. - In the Path mappings section, map your local
src/directory to/app/srcinside the container. - Start the container with
docker compose up, then click the debug button in WebStorm.
Enabling Debugging in Visual Studio Code
Add the following configuration to your .vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "attach",
"name": "Attach to Docker",
"address": "localhost",
"port": 9229,
"localRoot": "${workspaceFolder}/src",
"remoteRoot": "/app/src",
"sourceMaps": true,
"skipFiles": ["<node_internals>/**"]
}
]
}Code language: JSON / JSON with Comments (json)
With the container running, press F5 in VSCode to attach. Breakpoints set in your .ts source files will be hit correctly because source maps are enabled.
Useful Boilerplate
If you want a ready-made starting point, the npm package boilerplate-typescript-rest-docker (source: npmjs.com) provides a pre-configured project scaffold covering TypeScript compilation, Docker setup, and debug configuration for both WebStorm and VSCode. Review its README for version-specific instructions before adopting it in a production project.
Key Takeaways
- Use a multi-stage Dockerfile to separate build-time and runtime dependencies.
- Enable
sourceMap: trueintsconfig.jsonso debuggers can map compiled JS back to TypeScript. - Expose port
9229and start Node with--inspect=0.0.0.0:9229for remote debugging. - Configure path mappings in your IDE to point from local source files to the container filesystem.
- Docker Compose makes it easy to manage the development container lifecycle alongside other services such as databases or caches.