beeman 🐝
Posted on August 7, 2020
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! 🐝
Posted on August 7, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.