Traduzir esta página

Integração com base de dados

To connect a database to an Express app simply load the desired Node.js driver or ORM in your app. The general pattern is shown below using SQLite as an example.

There are also a few best practices to follow, regardless of the database you’re using.

SQLite

Node.js includes a built-in SQLite client, node:sqlite, available without a flag since Node.js 22.13.0. No installation is required. For older Node.js versions, better-sqlite3 offers a similar synchronous API.

Exemplo

This example creates a sample user that you can retrieve at http://localhost:3000/users/1. It uses a synchronous SQLite API for simplicity: long-running queries block the thread handling requests. For workloads with long-running queries, consider moving database work to a worker thread or using an asynchronous database driver.

index.cjs
const express = require('express');
const { DatabaseSync } = require('node:sqlite');
const app = express();
const db = new DatabaseSync('app.db');
db.exec('CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT NOT NULL)');
db.prepare('INSERT INTO users (id, name) VALUES (?, ?) ON CONFLICT(id) DO NOTHING').run(1, 'Alice');
const findUser = db.prepare('SELECT id, name FROM users WHERE id = ?');
app.get('/users/:id', (req, res) => {
const user = findUser.get(req.params.id);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user);
});
app.listen(3000);

Other databases

Every other database follows the same pattern: install the Node.js driver from npm, create a client or connection pool once when the app starts, and use it in your route handlers. Refer to the official documentation of each driver for connection options and API details:

For any database not listed here, search for its driver on the npm site.

ORMs and query builders

The example above uses a database driver directly, which is a good fit for small apps and for learning how things work. As an application grows, an ORM (object-relational mapper) or a query builder can take over concerns such as schema definitions, migrations, relations between tables, and input mapping, while reducing boilerplate. Many of them also provide type safety when used with TypeScript.

Popular options in the Node.js ecosystem include:

  • Prisma: a type-safe ORM with a schema-first workflow and built-in migrations. Supports PostgreSQL, MySQL, SQLite, SQL Server, MongoDB, and CockroachDB.
  • Drizzle ORM: a lightweight, TypeScript-first ORM with a SQL-like query API and a migrations toolkit.
  • Sequelize: a mature, promise-based ORM for PostgreSQL, MySQL, MariaDB, SQLite, and SQL Server.
  • TypeORM: an ORM built around decorators and entities, supporting many SQL databases and MongoDB.
  • Mongoose: an ODM (object document mapper) for MongoDB.
  • Knex.js: a flexible SQL query builder with migrations support, useful when you want control over your queries without writing raw strings.
  • Kysely: a type-safe SQL query builder for TypeScript.

Each ORM has its own API, supported databases, and approach to schemas, migrations, and queries. Features and setup requirements vary, so consult each project’s documentation before choosing one. Query builders keep you close to SQL, while full ORMs abstract more of it away. Pick the level of abstraction that matches your team and project, and prefer tools that are actively maintained.

Best practices

Adapt these practices to the database and library you choose. The snippets below illustrate separate parts of an application. The PostgreSQL snippets assume you have installed the pg driver and created a shared pool:

import { Pool } from 'pg';
const pool = new Pool({ connectionString: process.env.DATABASE_URL });

Create connections once and reuse them

Opening a database connection is expensive. Many drivers provide a connection pool that creates connections as needed and reuses them across requests. Create the pool (or client) once when the app starts. Acquire connections from that shared pool as needed in your route handlers, for example for a transaction, and always release them in a finally block. For individual queries, pool.query() in pg handles acquiring and releasing a connection for you.

Use parameterized queries

Never build SQL queries by concatenating or interpolating user input into the query string. Doing so exposes your app to SQL injection. Use your driver’s parameterized queries or prepared statements to pass user input separately from the query itself.

// Vulnerable: user input becomes part of the query
const user = await pool.query(`SELECT * FROM users WHERE id = ${req.params.id}`);
// Safe: user input is passed as a parameter
const user = await pool.query('SELECT * FROM users WHERE id = $1', [req.params.id]);

Also validate user input before it reaches your queries, both for security and for data integrity. For NoSQL databases, construct queries using the driver’s API and validate the types, fields, and operators you accept. Do not pass user-supplied query objects directly to the database.

Keep credentials out of your code

Never hardcode connection strings, usernames, or passwords in your source code, and never commit them to version control. Read them from environment variables or a secrets manager instead:

const pool = new Pool({ connectionString: process.env.DATABASE_URL });

During development, you can keep these values in a .env file and load it with Node.js’s built-in support for environment files, with no extra dependencies:

Terminal window
node --env-file=.env index.js

Make sure .env is listed in your .gitignore file so it never gets committed.

Handle database errors

Database calls fail: connections drop, queries time out, constraints are violated. In Express 5, errors thrown in async route handlers are passed to your error-handling middleware automatically, so define one and decide there what to expose to clients. Avoid leaking internal error details, such as query text or stack traces, in API responses.

Cancel work when the client disconnects

Since Node.js 24.20.0, every request exposes req.signal, an AbortSignal that is aborted when the client disconnects. Pass it to any cancellation-aware API, such as a database driver that supports abort signals, so your app stops doing work nobody is waiting for:

app.get('/reports', async (req, res) => {
const report = await generateReport({ signal: req.signal });
res.json(report);
});

Close connections on shutdown

When your app receives a termination signal, stop accepting new requests and close your database connections so in-flight queries can finish cleanly:

const server = app.listen(3000);
process.on('SIGTERM', () => {
server.close(async () => {
await pool.end();
process.exit(0);
});
});

If you already use an AbortSignal to coordinate cancellation across your app, you can pass it to app.listen and abort it instead of calling server.close() directly.