A structured and extensible Node.js + Express + TypeScript backend starter designed for building REST APIs with a clean project architecture, centralized error handling, logging, security middleware, environment-based configuration, and development tooling already configured.
This repository provides a starting point for building maintainable Node.js backend applications with TypeScript.
Instead of starting with a single Express file, the project separates application responsibilities into dedicated layers for:
- Routes
- Controllers
- Services
- Middleware
- Configuration
- Constants
- Utilities
The starter also includes common backend concerns such as request logging, rate limiting, security headers, sessions, cookies, CORS, compression, CSRF protection, 404 handling, and centralized error handling.
- Node.js + Express
- TypeScript with strict mode
- Modular backend architecture
- Controller/service separation
- Environment-based configuration
- Development auto-reload with Nodemon
- TypeScript execution with
ts-node @/path aliases- Centralized error handling
- Custom 404 middleware
- HTTP request logging
- Winston application logging
- Rate limiting
- Helmet security headers
- CORS support
- Response compression
- Cookie parsing
- Express sessions
- CSRF protection
- Static file serving
- Graceful process-level error logging
- Production TypeScript build output
- Node.js
- TypeScript
- Express
- Helmet
- CORS
- CSRF
- Cookie Parser
- Express Session
- Compression
- Limiter
- Nodemon
- ts-node
- tsconfig-paths
- cross-env
- rimraf
- Winston
- dotenv
typescript-nodejs-starter/
├── src/
│ ├── config/
│ │ ├── express.config.ts
│ │ └── index.ts
│ │
│ ├── constants/
│ │
│ ├── controller/
│ │ └── auth/
│ │ └── index.ts
│ │
│ ├── middlewares/
│ │ ├── errorHandler.ts
│ │ ├── notFountHandler.ts
│ │ ├── rateLimit.ts
│ │ ├── requestLogger.ts
│ │ └── sessionHandler.ts
│ │
│ ├── routes/
│ │ ├── auth.ts
│ │ └── index.ts
│ │
│ ├── services/
│ │ └── auth/
│ │ └── index.ts
│ │
│ ├── utils/
│ │
│ ├── app.ts
│ └── server.ts
│
├── .env.local
├── .gitignore
├── nodemon.json
├── package.json
├── package-lock.json
├── tsconfig.json
└── README.md
The starter follows a simple layered architecture:
HTTP Request
│
▼
Route
│
▼
Controller
│
▼
Service
│
▼
Database / External Service
│
▼
Controller
│
▼
HTTP Response
Routes define the API endpoints and map requests to controllers.
src/routes/
Controllers handle HTTP-specific logic such as:
- Reading request data
- Calling services
- Returning responses
- Forwarding errors
src/controller/
Services contain application and business logic.
src/services/
This separation makes the application easier to test, maintain, and extend.
The Express application currently applies middleware in approximately the following order:
Request
│
▼
Request Logger
│
▼
Body Parser
│
▼
Static Files
│
▼
Helmet
│
▼
Compression
│
▼
Cookie Parser
│
▼
CORS
│
▼
CSRF Protection
│
▼
Session Handling
│
▼
Rate Limiter
│
▼
API Routes
│
▼
404 Handler
│
▼
Error Handler
All application routes are mounted under:
/api
Make sure you have installed:
- Node.js 20+ recommended
- npm
- Git
Check your installed versions:
node --version
npm --versiongit clone https://github.com/Naveedahmedtech/typescript-nodejs-starter.gitcd typescript-nodejs-starternpm installThe application loads environment variables based on NODE_ENV.
The expected naming convention is:
.env.local
.env.dev
.env.prod
For example:
NODE_ENV=localloads:
.env.local
The repository currently includes:
PORT=8000PORT=8000As the application grows, additional environment variables can be added here:
PORT=8000
SESSION_SECRET=your-secret
DATABASE_URL=your-database-urlNever commit production secrets, passwords, API keys, access tokens, or database credentials to Git.
Run the project using the local environment:
npm run localThis starts the TypeScript application with Nodemon and reloads the server whenever source files change.
The default server port is:
8000
So the application will normally be available at:
http://localhost:8000
Run:
npm run devThis sets:
NODE_ENV=dev
and attempts to load:
.env.dev
Create .env.dev if you want a dedicated development configuration:
PORT=8000npm run localUses:
NODE_ENV=local
with Nodemon and ts-node.
npm run devUses:
NODE_ENV=dev
npm run buildRemoves the previous dist directory and compiles the TypeScript source.
Compiled files are written to:
dist/
The project contains a production start script, but it should be reviewed before production deployment. See the Production Notes section below.
The API is mounted under:
/api
Authentication-related routes are mounted at:
/api/auth
POST /api/auth/registerThe request body is forwarded to the registration service.
Example JSON payload:
{
"name": "John Doe",
"email": "john@example.com"
}A successful controller response uses status:
201 Created
The current registration service is an example implementation only.
It does not currently:
- Store users in a database
- Hash passwords
- Validate credentials
- Generate authentication tokens
- Persist data between requests
- Check whether users already exist
It should therefore be treated as starter/demo code and replaced with real authentication logic before production use.
CSRF middleware is enabled globally using cookies.
Because of this, state-changing requests such as:
POST
PUT
PATCH
DELETEwill require valid CSRF handling.
For a frontend application, a common next step is to create an endpoint such as:
GET /api/csrf-tokenthat returns a CSRF token to the client.
The client can then include the token with protected requests.
The project uses TypeScript strict mode:
{
"strict": true
}Source files are located in:
src/
Compiled JavaScript is generated in:
dist/
The project targets:
ES2020
and uses:
NodeNext
module behavior.
Instead of deeply nested imports such as:
import logger from "../../../utils/logger";the project supports:
import logger from "@/utils/logger";The alias is configured as:
{
"baseUrl": "./src",
"paths": {
"@/*": ["*"]
}
}During development, tsconfig-paths/register is loaded through Nodemon to resolve these aliases.
The project includes dedicated middleware for:
Unknown endpoints are forwarded to a custom not-found handler.
src/middlewares/notFountHandler.ts
Unhandled application errors are processed by:
src/middlewares/errorHandler.ts
This keeps route and controller implementations cleaner and provides a central place for API error responses.
Application logging is handled with Winston.
The server also listens for process-level errors including:
uncaughtException
and:
unhandledRejection
This provides a central mechanism for recording unexpected runtime failures.
Several security-related middleware packages are already integrated.
helmet
Adds security-related HTTP headers.
cors
Controls cross-origin requests.
csurf
Adds Cross-Site Request Forgery protection.
The project includes custom rate-limit middleware to reduce excessive request traffic.
cookie-parser
express-session
provide cookie and session handling.
Express's default:
X-Powered-By
header is disabled.
HTTP responses are compressed using:
compression
which can reduce response sizes and improve transfer performance.
Before deploying this starter in production, review the following.
The current start command should be corrected so cross-env launches Node rather than Node trying to execute cross-env.
A typical command would be:
{
"start": "cross-env NODE_ENV=prod node ./dist/server.js"
}TypeScript's paths configuration helps the compiler and development tooling resolve:
@/*
but TypeScript itself does not automatically rewrite those aliases in emitted JavaScript.
Before production deployment, configure a runtime alias strategy or rewrite aliases during the build process.
Possible approaches include:
tsc-alias- Node package imports
- A bundler
- Relative imports
- Another runtime alias solution
Replace the example registration implementation with:
- Database persistence
- Request validation
- Password hashing
- Authentication tokens or secure sessions
- Duplicate account detection
- Authorization logic
Production session handling should include:
- A secure session secret
- Persistent session storage
- Appropriate cookie settings
securecookies over HTTPS- Suitable
sameSiteconfiguration
The default in-memory session store should not be used for a production application.
Avoid unrestricted CORS for a production API.
Configure trusted frontend origins explicitly.
Example:
cors({
origin: "https://example.com",
credentials: true,
});Consider validating environment variables during application startup using a library such as:
Zod
Joi
envalid
This prevents the application from starting with invalid or missing configuration.
Useful additions for turning this starter into a production backend include:
- PostgreSQL or MongoDB integration
- Prisma, Drizzle, TypeORM, or Mongoose
- Request validation with Zod
- User authentication
- Password hashing with bcrypt or Argon2
- JWT or secure session authentication
- Role-based authorization
- Unit testing
- Integration testing
- ESLint
- Prettier
- API documentation with Swagger/OpenAPI
- Docker support
- GitHub Actions CI/CD
- Health-check endpoint
- Graceful server shutdown
- Structured environment validation
As the application grows, features can follow the existing route → controller → service pattern:
src/
├── routes/
│ ├── auth.ts
│ ├── users.ts
│ └── products.ts
│
├── controller/
│ ├── auth/
│ ├── users/
│ └── products/
│
└── services/
├── auth/
├── users/
└── products/
This keeps HTTP handling separate from business logic.
Contributions are welcome.
git checkout -b feature/my-featuregit commit -m "feat: add my feature"git push origin feature/my-featureThis project is licensed under the ISC License.
Naveed Ahmed
GitHub: @Naveedahmedtech
Repository: github.com/Naveedahmedtech/typescript-nodejs-starter
If this starter helps you build your next Node.js API, consider giving the repository a ⭐.
Issues, suggestions, and contributions are welcome.