Connecting to PostgreSQL with node-postgres, parameterized queries, SQL injection prevention, and a first look at Prisma ORM.
Module F-7 — Working with Databases from Node.js
What this module covers: Almost every Node.js application talks to a database. This module covers the two dominant approaches: using
node-postgres(pg) directly for full control with raw SQL, and using Prisma ORM for a type-safe, schema-driven workflow. You will learn how to connect, query, parameterize inputs to prevent SQL injection, manage connection pooling, and handle errors correctly. By the end you will have a working database layer you can drop into the Express API from F-6.
Two Approaches: Raw SQL vs ORM
Before writing code, choose your approach:
Raw SQL with node-postgres
- Full control over every query
- No abstraction layer — what you write is what runs
- Best when queries are complex, performance-critical, or team is SQL-proficient
- Requires manual migration management
Prisma ORM
- Type-safe queries with autocomplete
- Schema-first: define your models in
schema.prisma, Prisma generates the client - Auto-generated migrations
- Faster to get started, excellent for standard CRUD
- Less control over complex queries (though raw SQL escape hatch exists)
Most production codebases use one or the other, or a mix (Prisma for standard queries, raw SQL for complex reports). This module teaches both.
Option 1: node-postgres (Raw SQL)
Installing and connecting
Why a Pool, not a single connection?
A database connection is expensive to open (~5–50ms). A connection pool keeps a set of connections open and reuses them. When your code needs a connection, it borrows one from the pool, uses it, and returns it. Under high load (100 concurrent requests), each request gets a connection instantly rather than waiting to open a new one.
pg.Pool handles this automatically. Never use new pg.Client() for a server — that creates a single connection with no pooling.
Basic queries
SQL Injection — Always Use Parameters
This cannot be overstated. Never concatenate user input into SQL strings:
With parameterized queries, pg sends the SQL template and the values to PostgreSQL separately. PostgreSQL treats the values as data, never as SQL. Even if a user sends '; DROP TABLE users; -- as their name, it is stored as a literal string, not executed.
Transactions
When multiple queries must succeed or fail together, use a transaction:
The finally block ensures the connection is always released back to the pool, even if an error occurs. Failing to release connections causes pool exhaustion — one of the most common production bugs.
Handling query errors
Common PostgreSQL error codes:
23505— unique constraint violation (duplicate email, etc.)23503— foreign key constraint violation42P01— table does not exist08006— connection failure
Option 2: Prisma ORM
Prisma is a TypeScript-first ORM. You define your schema in schema.prisma, run a migration to create the tables, and Prisma generates a fully-typed client.
Installing Prisma
Defining the schema
Running migrations
Using the Prisma Client
Handling Prisma errors
Common Prisma error codes:
P2002— unique constraint violationP2003— foreign key constraint violationP2025— record not found (forfindUniqueOrThrow,update,delete)
Connecting the Database Layer to Express
Putting it all together with the Express router from F-6:
Which Should You Use?
| Scenario | Recommendation |
|---|---|
| New project, TypeScript, standard CRUD | Prisma |
| Complex analytical queries, heavy JOINs, window functions | Raw SQL (pg) |
| Team is SQL-proficient | Raw SQL or Prisma with $queryRaw |
| Team is JS-first, less SQL experience | Prisma |
| Performance-critical, fine-grained query control | Raw SQL |
| Rapid prototyping | Prisma |
Many production applications use both: Prisma for standard CRUD, and pool.query() for complex reports and bulk operations that would be awkward with an ORM.
Summary
node-postgres(pg) gives you direct SQL access. Create aPool— never a singleClient— for connection reuse. Parameters are$1,$2, etc.- Always use parameterized queries. Never concatenate user input into SQL strings. This is how SQL injection happens.
- Transactions require a dedicated
clientfrom the pool. Alwaysclient.release()in afinallyblock. - Prisma is schema-first. Define models in
schema.prisma, runprisma migrate dev, import and use the generated client. Fully typed. - Prisma error codes (
P2002,P2025) are how you detect constraint violations and missing records. - Connect the DB layer to Express by calling your data functions inside route handlers and passing errors to
next(err).
Next: building your first complete real-world application — pulling everything from F-1 through F-7 into a structured project.
Why are parameterized queries (e.g., WHERE name = $1) essential when using raw SQL via node-postgres?
What is the primary advantage of using a database connection pool (pg.Pool) instead of managing single connections (pg.Client)?
When executing a SQL transaction in node-postgres, why must you call client.release() in a finally block?
Test your knowledge with more question sets
Sign in to access a wider variety of questions and get notified when new practice sets are added to this module.
Sign in & RegisterDiscussion
0Join the discussion