As part of my 100 day of code challenge, I am building a web app using NestJS (and a not yet specified frontend framework). So one of the first things I tried setting up was the database connection. I plan on using a mongodb and because I might switch between different devices for development, I decided to sign up for a free account on a shared cluster at mongodb.com.
This is a longer description of my journey with this. If you are only interested in the results, click here.
First steps with the db
Since I’m new to NestJS (and developing with Node as a backend in general) I started this, by following the frameworks documentation. Although there is support for mongoose(link: https://mongoosejs.com), I decided to go with TypeORM. TypeORM is an Object Relational Mapper, that is written in TypeScript and since NestJS offers an integration, I felt like this would be a good choice. Another reason is, that I have no experience with mongodb, so if, for some reason, I decide, I won’t be using it for this project, it seems like it will be much easier changing databases with TypeORM, than if I would have used mongoose.
So following the documentation, I started by adding the database configuration to the app.module.ts
like this:
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'mongodb',
url: 'mongodb+srv://<username>:<password>@some.subdomains.of.mongodb.net/<databasename>'
})
],
controllers: [AppController],
providers: [AppService],
})
When I then started the server, the connection worked. Nice, this was easy!
Sadly at that point I stopped reading the documentation and started looking at the code. I realised, that I have to commit this to a public repository and I maybe shouldn’t commit login data to a publicly hosted database (actually, I think, no database connection data, publicly hosted or not, should go into a git repository).
Looking for options to configure
So instead of reading the next few paragraphs in the database part of the NestJS documentation, I started to search for options on how to do this configuration outside of the code, in a configuration- or environment-file, that I simply don’t have to commit.
So instead of just reading about the ormconfig.json file, that is a way to configure TypeORM I looked for different options. And I found one, the config module of NestJS.
It internally uses dotenv, which is used to load the content of a file called .env
into the Node process.env. This is a common approach in Node.js and is useful to quickly swap the applications environment. The .env
-file holds key-value pairs to represent the environment.
The configuration module of NestJS offers different ways to access and to store environment variables, ranging from different locations for the .env
-file, to ignore the file and only use the runtime environment variables or even use a custom environment file, that allows for a more complex structure such as yaml or json. In this case, the values are parsed into key-value pairs for use in the process.env.
There is a lot more advanced options for more complex situations, but these are the basic options, that the module offers for a simpler use case.
So I again, opened the documentation of NestJS regarding the config module and ended up including it in the app.module.ts like this:
@Module({
imports: [
ConfigModule.forRoot()
})
],
controllers: [AppController],
providers: [AppService],
})
For a first test, my .env
file only included one value:
testvalue=testvalue
As I wanted to see this in action, I changed the bootstrapped app.service.ts
to look like this:
@Injectable()
export class AppService {
constructor(private configService: ConfigService) {}
getHello(): string {
console.log(this.configService.get<string>('testvalue'));
return 'Hello World!' + this.configService.get<string>('testvalue');
}
}
This loads the value of the key ‘testvalue’ from the .env
file and it works as expected. Nice!
Now one could be so naive to just think, well let’s use this in my app.module.ts
. That was exactly what I did, but I didn’t get it to work (and I found some examples on the internet, so I think this is more a me problem). So I look into alternatives and found the ormconfig.json
(yes, the one I could have found immediately …).
Configuration using the ormconfig.json
So I actually found this in the documentation of TypeORM and because, when I tried to configure TypeORM in the app.module.ts
with the config module, I got this error:
Error: No connection options were found in any orm configuration files.
Searching for this pointed me in the right direction. Configuring the database connection with the ormconfig.json
file (or a lot of other file formats) is done by creating the file in the root of the project. In my case, I created an ormconfig.json
and it looks like this:
{
"type": "mongodb",
"url": "mongodb+srv://<username>:<password>@some.subdomains.of.mongodb.net/<databasename>"
}
If this configuration file is used, it is important to not put any configuration in the app.module.ts
. One problem of this is, that it is an additional configuration file and since I don’t want too many different config files, I’d prefer not to create one only for the database. Another point is that I maybe want to run this in a container and then configure it from Environment variables for example. So this solution might not fit my needs.
I also discovered the option to configure TypeORM from the environment variables directly, by using the keys TypeORM provides. This would work for me, but there is one thing, that is not possible with this configuration (and most likely I won’t need it for this project, but better having it, than needing it, right?). I can not have more than one database configured.
Configuration using a configuration service in NestJS
So what I ended up doing was inspired here. This describes an injectable service, that implements the TypeOrmOptionsFactory
interface, that can be used to asynchronously configure TypeORM. I combined this with the ConfigModule
of NestJS (and I guess, it would be possible to use other config options as well). The service that configures my mongodb looks like this:
@Injectable()
export class MongoDBConfigService implements TypeOrmOptionsFactory {
constructor(private configService: ConfigService) {}
createTypeOrmOptions(): TypeOrmModuleOptions {
return {
type: 'mongodb',
url: this.configService.get<string>('MONGODB_URL'),
username: this.configService.get<string>('MONGODB_USER'),
password: this.configService.get<string>('MONGODB_PASSWORD'),
};
}
}
In this service I can add all the configuration parameters I need and I can just create one service per database.
Conclusion
This was an unnecessary long journey. I think I could have avoided part of it, by just reading the next few instructions or doing a little more research. On the other hand, I figured out a solution myself. From my point of view it works good and is really flexible, but I also don’t know much about NestJS and TypeORM so this is only a feeling I can’t prove.
Top comments (1)
Thanks for this great post. Exactly where I am right now.