Express does not prescribe a folder layout, while NestJS has clear conventions for organizing code into modules. This chapter walks through the files nest new generates, the key configuration, feature modules, and a common way to organize an Express project.
The default Nest project
A project created with nest new looks roughly like this (ESLint and Prettier config omitted).
hello-nest/
├── src/
│ ├── app.controller.spec.ts
│ ├── app.controller.ts
│ ├── app.module.ts
│ ├── app.service.ts
│ └── main.ts
├── test/
│ ├── app.e2e-spec.ts
│ └── jest-e2e.json
├── nest-cli.json
├── package.json
├── tsconfig.build.json
└── tsconfig.json
Configuration files
nest-cli.json controls the Nest CLI. sourceRoot points to the source folder, and deleteOutDir clears dist before each build.
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"deleteOutDir": true
}
}
In tsconfig.json, two options are essential for Nest: experimentalDecorators enables decorator syntax such as @Controller(), and emitDecoratorMetadata keeps constructor parameter types at runtime so dependency injection knows which class to provide. tsconfig.build.json excludes test files from the build.
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"outDir": "./dist",
"baseUrl": "./"
}
}
This shows only the Nest-related options. The generated file also sets module, target, strictNullChecks and more, and defaults vary slightly between Nest releases.
Splitting code into feature modules
As an application grows, each feature gets its own module. nest g resource creates the controller, service, module, DTOs and entity in one go, and for a REST API it also scaffolds CRUD endpoints.
nest g resource users
src/users/
├── dto/
│ ├── create-user.dto.ts
│ └── update-user.dto.ts
├── entities/
│ └── user.entity.ts
├── users.controller.spec.ts
├── users.controller.ts
├── users.module.ts
├── users.service.spec.ts
└── users.service.ts
By convention, file names follow <name>.<type>.ts in kebab case, and classes use PascalCase such as UsersController and UsersService. A module declares its own controllers and providers and lists in exports only the providers other modules need.
// src/users/users.module.ts
import { Module } from "@nestjs/common";
import { UsersController } from "./users.controller";
import { UsersService } from "./users.service";
@Module({
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}
The CLI adds UsersModule to the root module's imports automatically. Shared guards, interceptors and utilities often live in a folder such as src/common/.
Organizing an Express project
Because Express enforces no structure, teams decide for themselves. A common approach splits routers, middleware and business logic into folders, and keeps creating the app separate from listening on a port.
my-express-api/
├── src/
│ ├── routes/
│ │ └── users.js
│ ├── middleware/
│ │ └── error-handler.js
│ ├── services/
│ │ └── users-service.js
│ ├── app.js
│ └── server.js
└── package.json
app.js only builds and exports the app; server.js opens the port. Tests can then import app without binding to a port.
// src/app.js
const express = require("express");
const usersRouter = require("./routes/users");
const app = express();
app.use(express.json());
app.use("/users", usersRouter);
module.exports = app;
// src/server.js
const app = require("./app");
const port = process.env.PORT || 3000;
app.listen(port, () => console.log(`Listening on ${port}`));
Summary
- A Nest project centers on
src/main.ts (entry point) and src/app.module.ts (root module). experimentalDecorators and emitDecoratorMetadata are required for decorators and dependency injection.- Give each feature its own module;
nest g resource generates the controller, service and DTOs together. - Express leaves structure to you: separate routers, middleware and services, and split
app from listen to make testing easy.
0 comments
Sign in · Sign in to leave a comment.