database connection
callback function
resource management
coding best practices
programming tips

close database connection after calling callback

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

If a database API uses callbacks, the connection must stay open until the asynchronous work and the callback-driven processing are actually finished. Closing the connection too early is a common bug in Node.js code, because the outer function returns before the query callback runs.

Core Sections

Understand the lifecycle problem

In callback-based code, this pattern is wrong:

javascript
1const connection = mysql.createConnection(config);
2connection.query("SELECT * FROM users", (err, rows) => {
3  console.log(rows);
4});
5connection.end();

connection.end() runs immediately after the query is scheduled, not after the callback completes. Depending on the driver and timing, you may see incomplete work, socket errors, or nondeterministic behavior.

Close the connection inside the final callback path

If you are managing a single connection directly, close it after the query callback has finished whatever follow-up work it needs to do.

javascript
1const mysql = require("mysql");
2const connection = mysql.createConnection(config);
3
4connection.query("SELECT * FROM users", (err, rows) => {
5  if (err) {
6    console.error(err);
7    connection.end();
8    return;
9  }
10
11  console.log(rows);
12  connection.end();
13});

That guarantees the connection stays open for the query lifecycle.

Handle nested async work carefully

Sometimes the query callback triggers more asynchronous work. In that case, do not close the connection until the true final callback completes.

javascript
1connection.query("SELECT * FROM users", (err, rows) => {
2  if (err) {
3    connection.end();
4    return;
5  }
6
7  doSomethingAsync(rows, (callbackErr) => {
8    if (callbackErr) {
9      console.error(callbackErr);
10    }
11    connection.end();
12  });
13});

The rule is simple: close the connection only after the last async step that still depends on it or on the query result flow.

Prefer pools for real applications

For request-driven servers, opening and closing one raw connection per operation is often not the best design. A connection pool is usually safer and more scalable. With a pool, you typically release the connection back to the pool instead of ending the whole underlying client every time.

javascript
1pool.getConnection((err, connection) => {
2  if (err) {
3    console.error(err);
4    return;
5  }
6
7  connection.query("SELECT * FROM users", (queryErr, rows) => {
8    connection.release();
9
10    if (queryErr) {
11      console.error(queryErr);
12      return;
13    }
14
15    console.log(rows);
16  });
17});

This pattern avoids exhausting the database with constantly created and destroyed connections.

Promises and finally are easier to reason about

If your driver supports promises, modernizing the code often makes resource handling clearer.

javascript
1async function loadUsers(pool) {
2  const connection = await pool.getConnection();
3  try {
4    const [rows] = await connection.query("SELECT * FROM users");
5    return rows;
6  } finally {
7    connection.release();
8  }
9}

This does not change the underlying rule. It just makes the cleanup path harder to forget.

Always close on error paths too

One of the easiest ways to leak connections is to close them on success but not on failure. Every branch that exits the operation should either end or release the connection appropriately.

That is why finally, or a single centralized completion callback, is so valuable.

Common Pitfalls

  • Calling end() right after scheduling the query, which closes the connection before the callback work is finished.
  • Closing the connection after the first callback even though later nested async work still depends on the result flow.
  • Opening raw connections for every request in a server application when a pool would be more appropriate.
  • Releasing or ending on the success path only and leaking connections when errors occur.
  • Mixing callback-style control flow with resource cleanup scattered across branches so the true final close point is hard to see.

Summary

  • In callback-based database code, the connection must remain open until the final async step completes.
  • Close or release the connection from the last callback path, not immediately after starting the query.
  • For multi-request applications, prefer a connection pool over one-off raw connections.
  • Promise-based code with try and finally usually makes cleanup easier to reason about.
  • Make sure error paths clean up resources just as reliably as success paths.

Course illustration
Course illustration

All Rights Reserved.