Alexander Nnakwue Software engineer. React, Node.js, Python, and other developer tools and libraries.

Getting started with NestJS, Vite, and esbuild

7 min read 2185

Getting started with NestJS, Vite, and esbuild

Introduction

In this article, we’ll learn about NestJS, Vite, and esbuild; how they work together; and how to configure a NestJS app to make use of both other tools as dependencies. In the process, we will get to learn how to work with them in real life scenarios, their major features, and use cases.

Jump ahead:

What is NestJS?

NestJS is a Node.js framework for building efficient and scalable enterprise server-side/backend applications. According to the documentation, it supports all latest ECMAScript versions of both JavaScript and TypeScript.

NestJS combines the well-known programming concepts and philosophies of OOP, functional programming, and functional reactive programming to solve the architectural challenges in the design of backend applications that are scalable, maintainable, easily testable, and are not tightly coupled together.

Although NestJS is platform independent and can work with any Node.js library if a binding is written for it, NestJS uses Express as a dependency by default, and can also be configured to use Fastify. This ease in configuring the framework via exposed APIs with other third-party modules makes it very easy for developers to customize the framework on a case-by-case basis.

What is Vite?

Vite is a build tool with lots of features, chief of which is a near-instant dev server startup time. It leverages the introduction of native ES modules in the browser and tooling written with languages that compile to native code to solve the issues with previous build tools (webpack, Parcel, etc.) relating to performance.

Vite works by first dividing the modules in an application into two categories, dependencies and source code, because dependencies rarely change during development. Vite pre-bundles these dependencies using esbuild under the hood. For source code that might need transforming (CSS, JSX, etc.), Vite serves them over native ESM to the browser.

As the browser makes the requests for the source code, Vite transforms and loads them on demand and the browser can use route-based code-splitting and conditional dynamic imports to bundle the needed code, making it a pretty fast process.

What is esbuild?

esbuild is a blazing fast JavaScript bundler written in Go and makes use of Go’s parallelism and ability to transform source code to machine code. Its features include, among others:

  • Huge plugin support
  • A minifier
  • TypeScript and JSX support
  • Both ES2015 and CommonJS module support
  • Tree-shaking capabilities

Installing and configuring a NestJS app

Now that we have looked at NestJS, Vite, and esbuild at a high level, let’s proceed to learn about how they work together by configuring a NestJS app to make use of both Vite and esbuild as dependencies. In the process, we‘ll learn how to work with them in real life scenarios, their major features, and prime use cases.



To get started with NestJS, go ahead and install the CLI, which bootstraps the starter code. This is an especially better option for those who are new to NestJS.

Another option is to clone the starter repo from GitHub. Note that to install the JavaScript flavor of the starter project, we can clone this repo, but you’ll need Babel to compile vanilla JavaScript).

For our purposes, we are going to go ahead and install the CLI. Run the following command:

npm i -g @nestjs/cli

After we are done installing the CLI, we can go ahead to create a new Nest project:

nest new nest_vite_esbuild_demo

The output of running this command is shown below:

nest new nest_vite_esbuild_demo                                                                                 took 24s
⚡  We will scaffold your app in a few seconds..

CREATE nest_vite_esbuild_demo/.eslintrc.js (665 bytes)
CREATE nest_vite_esbuild_demo/.prettierrc (51 bytes)
CREATE nest_vite_esbuild_demo/README.md (3340 bytes)
CREATE nest_vite_esbuild_demo/nest-cli.json (118 bytes)
CREATE nest_vite_esbuild_demo/package.json (2007 bytes)
CREATE nest_vite_esbuild_demo/tsconfig.build.json (97 bytes)
CREATE nest_vite_esbuild_demo/tsconfig.json (546 bytes)
CREATE nest_vite_esbuild_demo/src/app.controller.spec.ts (617 bytes)
CREATE nest_vite_esbuild_demo/src/app.controller.ts (274 bytes)
CREATE nest_vite_esbuild_demo/src/app.module.ts (249 bytes)
CREATE nest_vite_esbuild_demo/src/app.service.ts (142 bytes)
CREATE nest_vite_esbuild_demo/src/main.ts (208 bytes)
CREATE nest_vite_esbuild_demo/test/app.e2e-spec.ts (630 bytes)
CREATE nest_vite_esbuild_demo/test/jest-e2e.json (183 bytes)

? Which package manager would you ❤️  to use? (Use arrow keys)
❯ npm 
  yarn 
  pnpm 

Make sure you have the latest Node.js version installed on your machine (except v13, which is not supported).

As we can see from the above, the project directory has been populated with Nest’s core files, dependencies, and base modules. Follow the steps and select the package manager of your choice. In this post, we’re using npm. The folder structure at the end of the installation is shown below.

The folder structure for scaffolding our NestJS app
The folder structure for scaffolding our NestJS app

Next, we can navigate into the folder by running the following command.


More great articles from LogRocket:


cd nest_vite_esbuild_demo

Then, go ahead and start the project:

npm run start

The output of running that command is shown below.

npm run start                  

> [email protected] start
> nest start

[Nest] 33031  - 08/08/2022, 3:22:16 AM     LOG [NestFactory] Starting Nest application...
[Nest] 33031  - 08/08/2022, 3:22:16 AM     LOG [InstanceLoader] AppModule dependencies initialized +48ms
[Nest] 33031  - 08/08/2022, 3:22:16 AM     LOG [RoutesResolver] AppController {/}: +8ms
[Nest] 33031  - 08/08/2022, 3:22:16 AM     LOG [RouterExplorer] Mapped {/, GET} route +3ms
[Nest] 33031  - 08/08/2022, 3:22:16 AM     LOG [NestApplication] Nest application successfully started +8ms

As an alternative to the above, we can also start our application in development mode to watch for file changes, recompile the build, and reload the dev server by running the command below:

npm run start:dev

Then, navigate to http://localhost:3000/ in the browser. Further, we can also go ahead and perform a manual installation of NestJS’s core dependencies. In this setup, we can set up our project structure as we wish, just by running the command below.

npm i --save @nestjs/core @nestjs/common reflect-metadata

Exploring the NestJS boilerplate

When we navigate to the src directory inside our project folder, we can see the default files created for us. We have:

  1. The app.controller.ts file, which represents our handler with just one route
  2. The app.service.ts file, which handles anything related to method or utility functions, so as to keep the controller slim
  3. The app.module file, which handles the base module for our application (a way of structuring our application components)
  4. The app.controller.spec.ts file, which handles testing our controller logic
  5. The main.ts file, which is entry point of our application and creates a Nest application instance using the core NestFactory function
The default files in our src directory
The default files in our src directory

NestJS encourages developers to keep their application architecture as modular as possible, with each directory inside the source directory representing a single module. Each Nest application must have at least one module, a root module, which is the starting point for building the application graph.

For example, we can run the following command to create a new module:

nest g module product_demo
Our new product demo module in the src directory
Our new product demo module in the src directory

As you can see from the above, we have created a product_demo module. We can then go ahead to create the other needed files — controller and services — and also import the product_demo module into the app base module.

For more information regarding NestJS fundamentals and features, we can make reference to the documentation, including testing, providers, lifecycle events, decorators, middleware, modules etc., to gain mastery of the framework. Next, let us go ahead and set up Vite and esbuild on our NestJS application.

Installing Vite and esbuild with NestJS

Vite is plugin-based and also comes with a well-optimized and fast build process, which can greatly improve your overall developer productivity and experience. It also supports TypeScript out of the box.

Now, let’s integrate Vite into our NestJS backend app. We are going to install Vite via the plugin, which runs a Node dev server with hot module replacement.

Run the command below as a development dependency.

npm install vite vite-plugin-node -D

Next, in the root of our project directory, we can create the vite.config.ts file, which configures our project to make use of the plugin. Let’s see the contents of that file below.

import { defineConfig } from 'vite';
import { VitePluginNode } from 'vite-plugin-node';
export default defineConfig({
  // ...vite configures
  server: {
    // vite server configs, for details see \[vite doc\](https://vitejs.dev/config/#server-host)
    port: 3000
  },
  plugins: [
    ...VitePluginNode({
      // Nodejs native Request adapter
      // currently this plugin support 'express', 'nest', 'koa' and 'fastify' out of box,
      // you can also pass a function if you are using other frameworks, see Custom Adapter section
      adapter: 'nest',
      // tell the plugin where is your project entry
      appPath: './src/main.ts',
      // Optional, default: 'viteNodeApp'
      // the name of named export of you app from the appPath file
      exportName: 'viteNodeApp',
      // Optional, default: 'esbuild'
      // The TypeScript compiler you want to use
      // by default this plugin is using vite default ts compiler which is esbuild
      // 'swc' compiler is supported to use as well for frameworks
      // like Nestjs (esbuild dont support 'emitDecoratorMetadata' yet)
      // you need to INSTALL `@swc/core` as dev dependency if you want to use swc
      tsCompiler: 'esbuild',
    })
  ],
  optimizeDeps: {
    // Vite does not work well with optionnal dependencies,
    // mark them as ignored for now
    exclude: [
        '@nestjs/microservices',
        '@nestjs/websockets',
        'cache-manager',
        'class-transformer',
        'class-validator',
        'fastify-swagger',
      ],
  },
});

Next, we need to update the entry point of our app server file (main.ts) to export the app named viteNodeApp (in the Vite config exportName field above) or any other name we have configured in the Vite config file above. See below.

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
if (import.meta.env.PROD) {
  async function bootstrap() {
    const app = await NestFactory.create(AppModule);
    await app.listen(3000);
  }
  bootstrap();
}

export const viteNodeApp = NestFactory.create(AppModule);

Next, in our package.json file, we can go ahead and add an npm script to run the dev server with the regular npm run dev command:

"scripts": {
  "dev": "vite"
}

As we have earlier mentioned, Vite comes with a dev server that serves our source files over native ESM. When we run our app with the Vite dev server using the npm run dev command after the entire setup, we’ll see the output below.

The output after running the npm run dev command
The output after running the npm run dev command

To be sure everything is still working as expected, run the test suite with the npm run test command and we can see the output below.

The output after running the npm run test command
The output after running the npm run test command

The plugin also uses Vite’s server-side rendering mode to build our app. To make use of this feature, we can go ahead and add a build script to our package.json file (using Vite to build our app instead of Nest):

"scripts": {
  "build": "vite build"
},

The vite build command bundles our code with Rollup and spits out highly optimized assets for our production environment. When we build our app with Vite by running the npm run build command after the entire setup, we can see the output below.

The output after running the npm run build command
The output after running the npm run build command

As mentioned before, Vite makes use of esbuild under the hood to transpile TypeScript to JavaScript. It is a very fast transpilation process — it can even be up to 20 times faster than regular TypeScript compilers, and HMR updates can reflect in the browser in under 50ms.

One of the advantages of using the Vite plugin (vite-plugin-node) is that we can choose to use either esbuild or swc to compile our TypeScript files, though we used only esbuild in this post.

Conclusion

In this post, we have learned how to get going with NestJS, Vite, and esbuild. As we have seen, NestJS has an interesting and rather new approach to building Node.js applications with a philosophy around OOP, FP, and FRP. These improvements greatly improve developer time and productivity in the long run, as we don’t have to bother about how to design our modules and components. NestJS solves that for us and proposes a one-module-per-folder pattern. We can also use the power of TypeScript to write our backend code.

Vite, on the other hand, is on a whole new level in the world of build tools. It is coming at a time where popular JS-based bundlers and build tooling in the NodeJS ecosystem needed a boost in terms of performance. Vite compiles our code to native code using a completely different approach from webpack and Parcel. As we have learned, it uses the browser and native ESM for bundling.

In all, we have combined these awesome technologies to build a simple boilerplate backend that can be a starting point for your next NestJS project.

Alexander Nnakwue Software engineer. React, Node.js, Python, and other developer tools and libraries.

One Reply to “Getting started with NestJS, Vite, and esbuild”

  1. Hi, really interesting article. Do you know if for Nestjs monorepos that use webpack this approach will work?

Leave a Reply