Guide For Nest JS
1. Getting Started - Create an Advanced Server
- Step 1: Create new project
Run the command:
nest new project-name
Followed by:
cd project-name
pnpm run start
You now have a running server on port 3000.
You can change the port number over at main.ts
.
You might wanna add the lines:
const PORT = process.env.PORT ?? 8000;
await app.listen(PORT, () => console.log(`server running on port ${PORT}`));
- Step 2: add dev script
In your package.json, add:
{
"dev": "nest start --watch",
}
- Step 3: Eslint
Nest is configured with the old eslint (v8), so you'll need to replace it with v9.
You'll need to uninstall all of these:
eslint
@typescript-eslint/eslint-plugin
@typescript-eslint/parser
eslint-config-prettier
eslint-plugin-prettier
and install these:
eslint
(latest)@eslint/js
eslint-plugin-perfectionist
eslint-plugin-react-compiler
globals
typescript-eslint
- Step 4: tsconfig.json
Copy the tsconfig from vs-vite-template
project, as it is more organized.
You'll need to manually change these however:
module
: set tocommonjs
instead ofESNEXT
noEmit
: set tofalse
instead oftrue
.emitDecoratorMetadata
: set totrue
instead offalse
.experimentalDecorators
: set totrue
instead offalse
.outDir
: set to "./dist"removeComments
: set totrue
instead offalse
.moduleResolution
: set toclassic
instead ofbundler
(or simply unset it).declaration
: set totrue
instead offalse
.
- Step 5: package.json type:commonjs
I'm 99% sure that nest can't work in esm. I tried to put "type": "module", and the dev server threw an esm related error. Therefore, make sure your package.json is set to:
{
"type": "commonjs"
}
- Step 6: use SWC
SWC (Speedy Web Compiler) is an extensible Rust-based platform that can be used for both compilation and bundling. Using SWC with Nest CLI is a great and simple way to significantly speed up your development process.
SWC is approximately x20 times faster than the default TypeScript compiler.
Install these packages:
p add -D @swc/cli @swc/core
And modify the nest-cli.json
:
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"builder": {
"type": "swc",
"options": {
"outDir": "dist",
"watch": false
}
},
"deleteOutDir": true,
"typeCheck": false // <--- in SWC, this defaults to false, and needs to be enabled manually.
}
}
SWC does not perform any type checking itself (as opposed to the default TypeScript compiler), so to turn it on, you need to use the --type-check flag, or you can just set the compilerOptions.typeCheck
property to true in your nest-cli.json.
SWC + Jest
To have support for jest in SWC, you first need to install:
p add -D jest @swc/core @swc/jest
Then update your package.json
:
{
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s?$": [
"@swc/jest"
]
},
"moduleNameMapper": {
"^@src/(.*)": "<rootDir>/$1"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}
And add a .swcrc
file at the root of your project, with the following contents:
{
"$schema": "https://json.schemastore.org/swcrc",
"sourceMaps": true,
"jsc": {
"parser": {
"syntax": "typescript",
"decorators": true,
"dynamicImport": true
},
"transform": {
"legacyDecorator": true,
"decoratorMetadata": true
},
"baseUrl": "./"
},
"minify": false
}
- Step 7: Configuration
- A. Installation
To be able to read .env
files, install the following:
p add @nestjs/config
The @nestjs/config package internally uses dotenv
.
Since @nestjs/config
relies on dotenv, it uses that package's rules for resolving conflicts in environment variable names. When a key exists both in the runtime environment as an environment variable (e.g., via OS shell exports like export DATABASE_USER=test) and in a .env file, the runtime environment variable takes precedence.
- B. Basic Usage
Once the installation process is complete, we can import the ConfigModule
in the root AppModule
and control its behavior using the .forRoot() static method:
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
@Module({
imports: [ConfigModule.forRoot()],
})
export class AppModule {
// ...
}
What this does:
- It looks for a file named
.env
at the root of your project, reads it, and adds its contents toprocess.env
. - The forRoot() method registers the
ConfigService
provider, which provides a get() method for reading these parsed/merged configuration variables. For example:const nodeEnv = configService.get<EnvOptions>('nodeEnv');
console.log(nodeEnv);
You can also specify a different path/name for the .env
files like so:
ConfigModule.forRoot({
envFilePath: '.env.development.local',
});
You can also specify multiple paths for .env files like so:
ConfigModule.forRoot({
envFilePath: ['.env.development.local', '.env.development'],
});
If a variable is found in multiple files, the first one takes precedence.
- C. Disable env variables loading
If you don't want to load the .env
file, but instead would like to simply access environment variables from the runtime environment (as with OS shell exports like export DATABASE_USER=test), set the options object's ignoreEnvFile
property to true
.
- D. Advanced Usage - Custom configuration files
For more complex projects, you may utilize custom configuration files to return nested configuration objects.
A custom configuration file exports a factory function that returns a configuration object. The configuration object can be any arbitrarily nested plain JavaScript object. The process.env
object will contain the fully resolved environment variable key/value pairs (with .env file and externally defined variables resolved and merged as described above). Since you control the returned configuration object, you can add any required logic to cast values to an appropriate type, set default values, etc. For example:
export default function getConfiguration() {
return {
port: parseInt(process.env.PORT, 10) || 3000,
database: {
host: process.env.DATABASE_HOST,
port: parseInt(process.env.DATABASE_PORT, 10) || 5432,
},
};
}
You can even add a validation schema to your configuration:
import joi from 'joi';
export const envVariablesSchema = joi.object({
PORT: joi.number().port(),
IS_DEV: joi.string(),
});
then...
@Module({
imports: [
ConfigModule.forRoot({
envFilePath: '.env', // <--- defaults to '.env'
isGlobal: true, // <--- defaults to false
cache: true, // <--- defaults to false
load: [getConfiguration],
validationSchema: envVariablesSchema,
}),
],
})
export class AppModule {
configure(consumer: MiddlewareConsumer) {
}
}
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { getConfiguration } from './config';
import { envVariablesSchema } from './config/validationSchema';
@Module({
imports: [
ConfigModule.forRoot({
envFilePath: '.env', // <--- defaults to '.env'
ignoreEnvFile: false, // <--- defaults to false.
isGlobal: true, // <--- defaults to false
cache: true, // <--- defaults to false
load: [getConfiguration],
validationSchema: envVariablesSchema,
}),
],
})
export class AppModule {
// ...
}
Then we can inject it using standard constructor injection:
import { ConfigService } from '@nestjs/config';
@Injectable()
export class UsersService {
constructor(private configService: ConfigService) {}
// ...
}
- E. Enable CORS
On the main file of main.ts
, add this line:
import { handleCors } from './common/utils/handleCors';
const nodeEnv = configService.get<EnvOptions>('nodeEnv');
app.enableCors({ origin: handleCors(nodeEnv), credentials: true });
And create a file src/common/utils/handleCors.ts
, with the contents of:
import { UnauthorizedException } from '@nestjs/common';
import { EnvOptions } from '@src/config/types';
const ALLOWED_DOMAINS = ['http://localhost:3000', 'https://luckylove.co.il'];
const PROD_DOMAIN_REGEX = '.luckylove.co.il';
export function handleCors(nodeEnv: EnvOptions) {
return (
origin: string,
callback: (err: Error | null, origin?: any) => void,
) => {
const isAllowed =
origin === undefined ||
ALLOWED_DOMAINS.includes(origin) ||
(nodeEnv !== EnvOptions.Prod && origin.endsWith(PROD_DOMAIN_REGEX));
if (isAllowed) return void callback(null, true);
callback(new UnauthorizedException('CORS not allowed'), false);
};
}
- F. Enable Cookie Parser
To enable cookie parser, first install:
p add cookie-parser
p add -D @types/cookie-parser
Once the installation is complete, apply the cookie-parser middleware as global middleware (for example, in your main.ts file).
import cookieParser from 'cookie-parser';
// somewhere in your initialization file
app.use(cookieParser());
- Step 8: Add a Custom Logger
- A. Introduction
Nest comes with a built-in text-based logger which is used during application bootstrapping and several other circumstances such as displaying caught exceptions. This functionality is provided via the Logger
class in the @nestjs/common
package.
You can also make use of the built-in logger, or create your own custom implementation, to log your own application-level events and messages.
- B. Basic Customization
To disable logging, set the logger
property to false
in the (optional) Nest application options object passed as the second argument to the NestFactory.create()
method.
const app = await NestFactory.create(AppModule, {
logger: false,
});
await app.listen(process.env.PORT ?? 3000);
To enable specific logging levels, set the logger
property to an array of strings specifying the log levels to display, as follows:
const app = await NestFactory.create(AppModule, {
logger: ['error', 'warn'],
});
await app.listen(process.env.PORT ?? 3000);
Values in the array can be any combination of 'log'
, 'fatal'
, 'error'
, 'warn'
, 'debug'
, and 'verbose'
.
To disable color in the default logger's messages, set the NO_COLOR
environment variable to some non-empty string.
- C. Custom Implementation
There are several way in which you can attach a logger to Nest. But the two most recommended ones are:
- Implementing the
LoggerService
interface - Extending the
ConsoleLogger
class
Extending the ConsoleLogger
:
import { ConsoleLogger } from '@nestjs/common';
export class MyLogger extends ConsoleLogger {
error(message: any, stack?: string, context?: string) {
// add your tailored logic here
super.error(...arguments);
}
}
Implementing the LoggerService
:
import { Injectable, LoggerService } from '@nestjs/common';
@Injectable()
export class MyLogger implements LoggerService {
/**
* Write a 'log' level log.
*/
log(message: any, ...optionalParams: any[]) {}
/**
* Write a 'fatal' level log.
*/
fatal(message: any, ...optionalParams: any[]) {}
/**
* Write an 'error' level log.
*/
error(message: any, ...optionalParams: any[]) {}
/**
* Write a 'warn' level log.
*/
warn(message: any, ...optionalParams: any[]) {}
/**
* Write a 'debug' level log.
*/
debug?(message: any, ...optionalParams: any[]) {}
/**
* Write a 'verbose' level log.
*/
verbose?(message: any, ...optionalParams: any[]) {}
}
- D. Logger as a module
For more advanced logging functionality, you'll want to take advantage of dependency injection. For example, you may want to inject a ConfigService
into your logger to customize it, and in turn inject your custom logger into other controllers and/or providers. To enable dependency injection for your custom logger, create a class that implements LoggerService
and register that class as a provider in some module.
import { Module } from '@nestjs/common';
import { MyLoggerService } from './myLogger.service';
@Module({
providers: [MyLoggerService],
exports: [MyLoggerService],
})
export class LoggerModule {}
With this construct, you are now providing your custom logger for use by any other module. Because your MyLoggerService
class is part of a module, it can use dependency injection (for example, to inject a ConfigService
).
- E. Logger on Nest load
There's one more technique needed to provide this custom logger for use by Nest for system logging (e.g., for bootstrapping and error handling).
Because application instantiation (NestFactory.create()
) happens outside the context of any module, it doesn't participate in the normal Dependency Injection phase of initialization. So we must ensure that at least one application module imports the LoggerModule
to trigger Nest to instantiate a singleton instance of our MyLoggerService
class.
We can then instruct Nest to use the same singleton instance of MyLoggerService
with the following construction:
const app = await NestFactory.create(AppModule, {
bufferLogs: true,
});
app.useLogger(app.get(MyLoggerService));
await app.listen(process.env.PORT ?? 3000);