Module F-7·20 min read

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

bash
javascript
bash

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

javascript

SQL Injection — Always Use Parameters

This cannot be overstated. Never concatenate user input into SQL strings:

javascript

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:

javascript

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

javascript

Common PostgreSQL error codes:

  • 23505 — unique constraint violation (duplicate email, etc.)
  • 23503 — foreign key constraint violation
  • 42P01 — table does not exist
  • 08006 — 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

bash

Defining the schema

prisma

Running migrations

bash

Using the Prisma Client

javascript
javascript

Handling Prisma errors

javascript

Common Prisma error codes:

  • P2002 — unique constraint violation
  • P2003 — foreign key constraint violation
  • P2025 — record not found (for findUniqueOrThrow, update, delete)

Connecting the Database Layer to Express

Putting it all together with the Express router from F-6:

javascript

Which Should You Use?

ScenarioRecommendation
New project, TypeScript, standard CRUDPrisma
Complex analytical queries, heavy JOINs, window functionsRaw SQL (pg)
Team is SQL-proficientRaw SQL or Prisma with $queryRaw
Team is JS-first, less SQL experiencePrisma
Performance-critical, fine-grained query controlRaw SQL
Rapid prototypingPrisma

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 a Pool — never a single Client — 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 client from the pool. Always client.release() in a finally block.
  • Prisma is schema-first. Define models in schema.prisma, run prisma 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.


Knowledge Check

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 & Register

Discussion

0

Join the discussion

Loading comments...

© 2026 Jatin Jain Saraf (JJS). All rights reserved.