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 |