Skip to main content

Setup Database and environment variables

You can get the codebase for the previous part by checking out the commit #4db24a

As announced, we will use MongoDB for our database, make sure you have configured a mongo cluster here and you have a DATABASE_URL ready to process.

To add MongoDB configuration to our project, we will use the MongooseModule module imported from @nestjs/graphql then we will configure environment variables to retrieve the DATABASE_URL variable.

Create a .env file and add there the key DATABASE_URL with the value you got from your cluster before we continue.

When working with NodeJS applications, it's common practice to use .env files to store key-value pairs for different environments. This makes it easy to switch between environments by simply swapping in the appropriate .env file.

A great way to implement this technique in a Nest application is to create a ConfigModule that provides access to a ConfigService. This service loads the appropriate .env file for the current environment. Although you could choose to build this module yourself, Nest offers the convenient @nestjs/config package out-of-the-box. We'll dive into this package in this section.

Let’s get started by installing the required packages:

# install required packages
$ yarn add @nestjs/mongoose @nestjs/config mongoose

Then, we will inject the MongooseModule module into our root module, as we are getting used to Nest syntaxes, the way we will inject it will look similar to how we did with the GraphQLModule previously.

In the app.module.ts file, let’s add this line, in the imports property of our root module:

// app/app.module.ts
...
MongooseModule.forRoot('YOUR_DATABASE_URL'),
...

The forRoot method takes the same parameters as the mongoose.connect() method, in this case, we are passing in the URL for our database.

Instead of hard-coding the URL, we can use the DATABASE_URL from our `.env` file.

We have installed the @nestjs/config package, which is a built-in package that exposes the ConfigModule**, w**e will use this in our root module, to configure the preload of our .env file with its variables.

By adding ConfigModule.forRoot() in the imports property of our root module, we are loading and parsing a `.env file from the default location (the project root directory). This merges key/value pairs from the**.**envfile with environment variables assigned toprocess.env. The result is stored in a private structure that can be accessed through a ConfigServiceinstance. We don't need to create this service ourselves, as@nestjs/config` takes care of it for us.

As we are loading environment variables, the connection to the MongoDB database will no longer be synchronous. To achieve an asynchronous connection, we will use the forRootAsync method instead of forRoot().

We will then use a factory provider to inject dynamic configuration, such as a dynamic database URL, into our mongoose.connect() function. I recommend looking at this resource to understand how to customize providers using the useFactory syntax.

Combining the information above, let’s update the imports property of our root module, here is how it should look now:

// app/app.module.ts
...
imports: [
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
playground: false,
plugins: [ApolloServerPluginLandingPageLocalDefault()],
}),
MongooseModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService) => {
const options: MongooseModuleOptions = {
uri: configService.get<string>('DATABASE_URL'),
};

return options;
},
}),
ConfigModule.forRoot({
cache: true,
}),
],
...

You can see that we've injected the ConfigService from the @nestjs/config package into the MongooseModule. This service contains a private structure that stores the key/value pairs from the .env file. Restart the server and ensure that the app is running.

In the same way we've injected environment variables into the root module, we can do the same for other modules throughout the app. We're slowly diving into the most important aspects of the Nest framework, great job!

Currently, this is how our root module looks like:

import { Module } from '@nestjs/common';
import { AppResolver } from './app.resolver';
import { AppService } from './app.service';
import { GraphQLModule } from '@nestjs/graphql';
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
import { join } from 'path';
import { ApolloServerPluginLandingPageLocalDefault } from 'apollo-server-core';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { MongooseModule, MongooseModuleOptions } from '@nestjs/mongoose';

@Module({
imports: [
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
playground: false,
plugins: [ApolloServerPluginLandingPageLocalDefault()],
}),
MongooseModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService) => {
// CHECK IF YOU GET WHAT IS EXPECTED
console.log('ENV VAR', configService.get('DATABASE_URL'));

const options: MongooseModuleOptions = {
uri: configService.get<string>('DATABASE_URL'),
};

return options;
},
}),
ConfigModule.forRoot({
cache: true,
}),
],
controllers: [],
providers: [AppService, AppResolver],
})
export class AppModule {}

Now, let's start building the other modules of the app.

You can get the codebase for the previous part by checking out the commit #812bc3