NestJS
Rspack can build Node.js applications such as NestJS. Compared with using tsc directly, Rspack can bundle application code, transform TypeScript with SWC, support development-time HMR, and keep runtime dependencies external when needed.
How to use
You can follow this document to manually add the required Rspack configuration to an existing NestJS project. The NestJS example in rstack-examples is also available as a reference, and contains a complete rspack.config.mjs, development script, production build script, and HMR entry.
Example
The rstack-examples repository contains a complete NestJS application built with Rspack.
Install dependencies
Install Rspack and the helper dependencies used by the NestJS example:
npm add @rspack/core @rspack/cli @rspack/dev-server webpack-node-externals run-script-webpack-plugin -D
yarn add @rspack/core @rspack/cli @rspack/dev-server webpack-node-externals run-script-webpack-plugin -D
pnpm add @rspack/core @rspack/cli @rspack/dev-server webpack-node-externals run-script-webpack-plugin -D
bun add @rspack/core @rspack/cli @rspack/dev-server webpack-node-externals run-script-webpack-plugin -D
deno add npm:@rspack/core npm:@rspack/cli npm:@rspack/dev-server npm:webpack-node-externals npm:run-script-webpack-plugin -D
For a minimal NestJS application, install the NestJS runtime dependencies as usual:
npm add @nestjs/common @nestjs/core @nestjs/platform-express reflect-metadata rxjs
yarn add @nestjs/common @nestjs/core @nestjs/platform-express reflect-metadata rxjs
pnpm add @nestjs/common @nestjs/core @nestjs/platform-express reflect-metadata rxjs
bun add @nestjs/common @nestjs/core @nestjs/platform-express reflect-metadata rxjs
deno add npm:@nestjs/common npm:@nestjs/core npm:@nestjs/platform-express npm:reflect-metadata npm:rxjs
Basic concepts
NestJS applications run in Node.js, so the Rspack configuration should target the Node.js runtime and preserve the framework metadata that NestJS reads at runtime.
- Node.js target (
target: 'node'): Generates output suitable for Node.js instead of the browser.
- Decorator metadata: NestJS relies on TypeScript decorators and emitted metadata for dependency injection, controllers, routes, guards, interceptors, and other framework features.
- External dependencies: Server applications usually do not need to bundle every package in
node_modules. Externalizing dependencies keeps the bundle smaller and allows Node.js to load packages normally at runtime.
- Development HMR: In development, the Node.js process needs to restart or accept updates after Rspack rebuilds the server bundle.
The following configuration is adapted from the NestJS example:
rspack.config.mjs
// @ts-check
import { defineConfig } from '@rspack/cli';
import { rspack } from '@rspack/core';
import { RunScriptWebpackPlugin } from 'run-script-webpack-plugin';
import nodeExternals from 'webpack-node-externals';
export default defineConfig({
context: import.meta.dirname,
target: 'node',
entry: {
main:
process.env.NODE_ENV === 'production'
? './src/main.ts'
: ['@rspack/core/hot/poll?100', './src/main.ts'],
},
output: {
clean: true,
},
resolve: {
extensions: ['...', '.ts', '.tsx', '.jsx'],
},
module: {
rules: [
{
test: /\.ts$/,
use: {
loader: 'builtin:swc-loader',
options: {
detectSyntax: 'auto',
jsc: {
parser: {
decorators: true,
},
transform: {
legacyDecorator: true,
decoratorMetadata: true,
},
},
},
},
},
],
},
optimization: {
minimizer: [
new rspack.SwcJsMinimizerRspackPlugin({
minimizerOptions: {
compress: {
keep_classnames: true,
keep_fnames: true,
},
mangle: {
keep_classnames: true,
keep_fnames: true,
},
},
}),
],
},
externalsType: 'commonjs',
plugins: [
process.env.NODE_ENV !== 'production' &&
new RunScriptWebpackPlugin({
name: 'main.js',
autoRestart: false,
}),
],
devServer: {
devMiddleware: {
writeToDisk: true,
},
},
externals: [
nodeExternals({
allowlist: [/@rspack\/core\/hot\/poll/],
}),
],
});
Use rspack dev for development and rspack build for production builds:
package.json
{
"scripts": {
"build": "rspack build",
"dev": "rspack dev",
"start": "node dist/main.js"
}
}
dev: Runs Rspack Dev Server, writes the server bundle to disk, and starts dist/main.js.
build: Creates a production bundle without the HMR polling entry.
start: Runs the production output with Node.js.
NestJS uses decorators such as @Module(), @Controller(), @Injectable(), and @Get(). Configure builtin:swc-loader to parse TypeScript decorators and emit decorator metadata:
rspack.config.mjs
export default {
module: {
rules: [
{
test: /\.ts$/,
use: {
loader: 'builtin:swc-loader',
options: {
detectSyntax: 'auto',
jsc: {
parser: {
decorators: true,
},
transform: {
legacyDecorator: true,
decoratorMetadata: true,
},
},
},
},
},
],
},
};
If your project also runs tsc, keep experimentalDecorators and emitDecoratorMetadata enabled in tsconfig.json so TypeScript tooling and Rspack's SWC transform follow the same assumptions:
tsconfig.json
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
Add @rspack/core/hot/poll only to the development entry, since including it in the production bundle will crash the Node.js application because HMR is not available in build mode:
rspack.config.mjs
export default {
entry: {
main:
process.env.NODE_ENV === 'production'
? './src/main.ts'
: ['@rspack/core/hot/poll?100', './src/main.ts'],
},
};
Because the server process is started from the generated bundle, devServer.devMiddleware.writeToDisk must be enabled:
rspack.config.mjs
export default {
devServer: {
devMiddleware: {
writeToDisk: true,
},
},
};
Add HMR handling to the NestJS bootstrap file so the old application instance is closed before the updated module is accepted:
src/main.ts
declare const module: any;
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
if (module.hot) {
module.hot.accept();
module.hot.dispose(() => app.close());
}
}
bootstrap();
RunScriptWebpackPlugin starts the generated main.js file during development:
rspack.config.mjs
import { RunScriptWebpackPlugin } from 'run-script-webpack-plugin';
export default {
plugins: [
new RunScriptWebpackPlugin({
name: 'main.js',
autoRestart: false,
}),
],
};
Externalize dependencies
Use webpack-node-externals to externalize packages from node_modules:
rspack.config.mjs
import nodeExternals from 'webpack-node-externals';
export default {
externalsType: 'commonjs',
externals: [
nodeExternals({
allowlist: [/@rspack\/core\/hot\/poll/],
}),
],
};
The HMR polling runtime must be allowlisted in development so it is bundled into the server output instead of being treated as an external dependency.
When minifying a NestJS server bundle, keep class names and function names. NestJS APIs such as execution context and metadata reflection can depend on stable class and handler function references.
rspack.config.mjs
import { rspack } from '@rspack/core';
export default {
optimization: {
minimizer: [
new rspack.SwcJsMinimizerRspackPlugin({
minimizerOptions: {
compress: {
keep_classnames: true,
keep_fnames: true,
},
mangle: {
keep_classnames: true,
keep_fnames: true,
},
},
}),
],
},
};
Native node modules
When building Node.js applications with Rspack, you may encounter dependencies that include Node.js native addon dependencies (.node modules). Because .node modules cannot be packaged into JavaScript artifacts, special handling is usually required. node-loader can be used to handle addon packaging.
rspack.config.mjs
export default {
module: {
rules: [
{
test: /\.node$/,
use: [
{
loader: 'node-loader',
options: {
name: '[path][name].[ext]',
},
},
],
},
],
},
};