Using plain pg with Nest.js
Intro
This is going to be a quick demo of how to get started using plain pg (node-postgres) with Nest.js — no ORMs in sight. Having found no simple guide on how to do this when I was looking for it — I figured it’d be worth writing about.
Demo on Github:
https://github.com/justin-calleja/nest-pg-demo
Using plain pg with Nest.js
Create a new Nest.js app — install pg — generate db module
1 | npx nest new nest-pg-demo |
Register a Provider
We’ll use a constants.ts file in /src to be able to import the DI token for our postgres connection (or pool of connections):
1 | // src/constants.ts |
The DI token (dependency injection token) is how you’ll declaratively tell the Inversion of Control (IoC) container which dependency you’d like injected. So you’ll use it to pull in a dependency from the container.
You’ll also use it when you register a provider with the container. Registering a provider is basically associating a DI token (which will be a string in our case) with a way to get a dependency. The container will use this “way to get a dependency” when you request the relevant dependency be injected.
Registering a provider looks like this:
1 | import { Module } from '@nestjs/common'; |
i.e. we need an object with the DI token and the way to get the dependency — the dbProvider — and pass it to providers in the Module configuration as shown above.
There’s a couple of ways to specify how to get the dependency in Nest.js. We’ll be using useValue:
1 | import { Pool } from 'pg'; |
Finally, we’ll also include the dbProvider in the DbModule’s exports so we’ll be able to use it in other modules importing DbModule:
1 | import { Module } from '@nestjs/common'; |
Use the Provider
You’ll notice that the AppModule is already importing DbModule (at least if you generated DbModule with the nest cli that should be the case):
1 | // src/app.module.ts |
This means that — in our AppModule i.e. in e.g. controllers and providers defined as part of AppModule) we’ll be able to ask Nest’s IoC container to inject a connection for us using the PG_CONNECTION DI token (string).
Doing so in our AppService looks something like this:
1 | // src/app.service.ts |
Notice that we annotate conn with Inject and make it private in the constructor. This is significant at it allows Nest to know that it should be the one to supply a value for conn and it also knows what to inject via the DI token.
getUsers is just a simple query to use as a sanity check. We’ll use it in AppController:
1 | // src/app.controller.ts |
I guess you might be wondering how we’re able to inject AppService here without the Inject annotation… That’s thanks to a shortcut (or “syntactic sugar” if you prefer). In Nest.js, registering a Provider with a class name:
1 | import { AppService } from './app.service'; |
… is equivalent to:
1 | import { AppService } from './app.service'; |
… which means — the DI token doesn’t have to be a string and now Nest.js knows what to inject when we type the private appService in AppController with the type AppService.
Start and seed postgres
Of course, you’re going to want postgres running if you want to try this out. If you have docker installed, take a look at the scripts and sql directories in the repo for this demo — but the gist is:
1 | #!/bin/bash |
scripts/start-db.sh starts postgres. --rm will remove the container when we stop it. By virtue of passing in the env vars we do — the container will auto create the somedb database. The volume mapping is so we keep our data even when we stop the container (and have it auto-removed).
After the db is up — you can run scripts/sync-db.sh:
1 | #!/bin/bash |
This should work regardless of your pwd when you run the script since we’re referencing the sql files relative to the location of the sync-db.sh file on disk (consider this to be an equivalent of __dirname in Node.js). Since schema.sql drops and re-creates the public schema every time — it’s safe to re-run sync-db.sh when you change your table definitions etc…
Start the app and GET /users
Finally, it’s time to run our app with npm run start:dev and hit the /users endpoint with:
1 | curl localhost:3000/users |