Skip to content

Connecting Express to Databases

Express itself has no built-in database layer, you choose a driver or ORM based on your database, and connect it during app startup. This lesson covers the patterns common to nearly every database integration before diving into specific databases.

General Connection Patterns

Almost every database library follows the same shape: connect once when the app starts (usually a connection pool, not a single connection), export that connection for use in your data access code, and close it gracefully on shutdown.

Connection details, host, credentials, database name, should always come from environment variables, never be hardcoded, so the same code works across development, staging, and production.

// db.js
require('dotenv').config();

async function connectDB() {
  // pseudocode shared shape across drivers
  const connection = await driver.connect(process.env.DATABASE_URL);
  console.log('Database connected');
  return connection;
}

module.exports = connectDB;

Every database lesson that follows (MongoDB, Mongoose, MySQL, PostgreSQL) fills in this same shape with a real driver.

Where Database Setup Fits in Your App

server.js
  -> connectDB()
  -> app.listen(PORT)

app.js
  -> imports routes (which import controllers, which import models/services)
  • Connect to the database before calling app.listen(), so the app doesn't accept traffic before it's ready.
  • Use connection pooling (built into most drivers) rather than opening a new connection per request.
  • Keep connection strings in .env, never commit real credentials to version control.
  • Handle connection failures explicitly, log clearly and exit rather than running in a broken state.

Database Integration Cheatsheet

A comparison of the databases covered in the following lessons.

Database Node Library Style
MongoDB mongodb (native driver) Document-based, flexible schema
MongoDB (ORM) mongoose Schema-based document modeling
MySQL mysql2 Relational, SQL-based
PostgreSQL pg Relational, SQL-based, advanced types

Why Connection Pooling Matters

Opening a brand-new database connection for every incoming HTTP request is slow and can exhaust the database's maximum connection limit under load. A connection pool keeps a small set of ready-to-use connections open and hands them out to queries as needed, reusing them instead of reconnecting each time.

Graceful Shutdown

Listening for termination signals and closing the database connection cleanly avoids abrupt disconnects that can leave queries in an inconsistent state, especially important during deployments that restart your process.

process.on('SIGTERM', async () => {
  await db.close();
  process.exit(0);
});

Common Mistakes

  • Opening a new database connection inside every route handler instead of reusing a pool.
  • Hardcoding database credentials directly in source code.
  • Not handling initial connection failures, letting the app start and accept traffic while unable to reach the database.
  • Skipping graceful shutdown, causing dropped connections during deploys or restarts.

Key Takeaways

  • Connect to the database once at startup using a pool, not per-request.
  • Keep connection configuration in environment variables.
  • Handle connection failures explicitly rather than letting the app silently run broken.
  • Close connections gracefully on shutdown for clean deploys and restarts.

Pro Tip

Add a /health route that checks database connectivity (a lightweight ping query) so your deployment platform and monitoring tools can detect a broken database connection independently of whether the Express process itself is still running.