Add a NestJS API to a Nx Workspace

beeman

beeman 🐝

Posted on August 7, 2020

Add a NestJS API to a Nx Workspace

Introduction

In this tutorial, we add two projects to the workspace, an application called api, and a library called core.

In this app, the Core library is responsible for providing the application-wide configuration using the @nestjs/config package. To validate the configuration we configure NestJS Config to use Joi.

1. Add the Nx Nest plugin.

Nx has a plugin system that provides functionality for various frameworks. There are official ones and community plugins. One of the official ones is the Nest plugin.

Run the following command to install the plugin:

yarn add -D @nrwl/nest

This plugin provides functionality to create Nest applications and libraries. Let's start with the application.

2. Create the API.

In this step we create an application called api.

2.1 Generate the api application

Run the following command to create the application:

nx generate @nrwl/nest:app api

This generates a new application in apps/api, and adds it as a project to workspace.json and nx.json.

2.2 Start the dev server

Run the following command to start the application

nx serve api

This starts the development server and the prints the message 'Listening at http://localhost:3333/api'.

When you open that URL in the browser, you are greeted with the following message:

{
  "message": "Welcome to api!"
}

3. Create the core library

One of the great things of building an app using Nx Workspace, is that you can separate the functionality of the app into libraries.

This has numerous benefits: it creates a separation of concerns, it allows members of a team to work on parts of the app in isolation with less risk of conflicts, and allows for re-using the libraries in other projects.

3.1 Generate the library

Run the following command to create the library:

nx generate @nrwl/nest:lib core

This generates a new library in libs/core, adds it as a project to workspace.json and nx.json.

Additionally, it adds an entry to the paths object in tsconfig.base.json, mapping the name @beehive/core to path libs/core/src/index.ts. This allows for using the library by importing it from the package name @beehive/core.

3.2 Use the library

Open apps/api/src/app/app.module.ts and add the following line on top, next to the other imports:

import { CoreModule } from "@beehive/core";

Next, add the CoreModule to the imports array in the @Module decorator:

@Module({
  imports: [CoreModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

Make sure to stop and start the dev server, so it will pick up the changes made in tsconfig.base.json.

When the server restarts, you should see that it loads the CoreModule:

[InstanceLoader] CoreModule dependencies initialized

💡 If you don't restart the dev server, it will not pick up the changes made in tsconfig.base.json and you will get an error like this:

ERROR in ./apps/api/src/app/app.module.ts
Module not found: Error: Can't resolve '@beehive/core'

Time to add some functionality to the core library.

4. Add the configuration.

4.1 Install the dependencies

Run the following command to install the dependencies and devDependencies:

yarn add @nestjs/config joi

4.2 Create the configuration file

Create the directory libs/core/src/lib/config, this is where the configuration is stored.

Next, create the file libs/core/src/lib/config/configuration.ts, and add the following snippet:

export const configuration = () => ({
  environment: process.env.NODE_ENV,
  port: parseInt(process.env.PORT || "3000", 10),
});

This file exports a method that returns an object with the configuration for the API.

The configuration object reads the values from the environment variables. Because environment variables are always represented as a string, the port property converts the value to an integer using parseInt.

Because the process.env.PORT variable can be undefined, the default string '3000' is provided. Leaving this out will make TypeScript's strict mode unhappy.

4.3 Create the validation file

Create the file libs/core/src/lib/config/validation.ts and add the following snippet:

import * as Joi from "joi";

export const validationSchema = Joi.object({
  NODE_ENV: Joi.string().valid("development", "production", "test").required(),
  PORT: Joi.number().default(3000),
});

The validation file exports a schema that uses Joi for validating the environment variables.

The PORT environment variable validates that the type is a number, and sets the default value.

For the NODE_ENV environment variable, the valid options are either development, production or test. We don't provide a default which means we should always apply one explicitly.

4.4 Tying it all together

Open libs/core/src/lib/core.module.ts and add the following imports at the top:

import { ConfigModule } from "@nestjs/config";
import { configuration } from "./config/configuration";
import { validationSchema } from "./config/validation";

After that, add a new array called imports to the @Module decorator, and add the ConfigModule like so:

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      load: [configuration],
      validationSchema,
    }),
  ],
  controllers: [],
  providers: [],
  exports: [],
})
export class CoreModule {}

When you have the server still running, the following error will appear:

Error: Config validation error: "NODE_ENV" is required

This is because we did not provide a default value for the NODE_ENV environment variable, which is required.

4.5 Add a .env file for development.

The Nest Config module uses dotenv under the hood. This means we can configure the environment using a .env file.

Create a new file called .env in your project root, and add the following snippet:

NODE_ENV=development
PORT=3000

Restart the dev server to make it read the .env file.

4.6 Update the .gitignore

Best practice is to not commit the .env file into git, as it might store sensitive information like API keys or database connections strings.

Open .gitignore from your project root and add the following line:

.env

💡 Because we don't include the .env file in the repository, it's considered a 'best practice' to provide a default .env.example file that can be used as a reference.

5. Use the configuration object.

Last thing to do is use the configuration in our api.

Open apps/api/src/main.ts and add the following imports at the top:

import { ConfigService } from "@nestjs/config";

Next, in the body of the bootstrap function, add the following to the start of the method, right below the definition of const app:

const app = await NestFactory.create(AppModule);
const config = app.get(ConfigService);

Lastly, we update the method of the listen method, and create a new line that invokes Logger.log, below the existin gone:

Logger.log("Listening at http://localhost:" + port + "/" + globalPrefix);
Logger.log(`Running in ${config.get("environment")} mode`);

When you start the dev server again, the following lines should appear in the output:

Listening at http://localhost:3000/api
Running in development mode

Awesome, the system works!

Summary

In this tutorial we created a Nest application called api, and a module called core.

The module is responsible for the application configuration and provides validation to make sure the required properties are defined and have the expected value.

We created a .env so we can easily apply environment vars during development, and added this file to .gitignore.

Lastly, we updated the main.ts file to make it use the ConfigService.

In the next tutorial, we will add a GraphQL endpoint to our API, stay tuned!

Thanks!

Thanks for reading my article, I hope it was useful. Feel free to reach out and follow me on Twitter or leave a comment on DEV! 🐝

💖 💪 🙅 🚩
beeman
beeman 🐝

Posted on August 7, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related