# How to Establish a Connection to SQL Server from Node.js Using mssql

> Learn best practices for connecting Node.js to SQL Server with mssql. Use ConnectionPool, async/await, and secure credentials for robust database access.

- Repository: [Node.js/node](https://github.com/nodejs/node)
- Tags: how-to-guide
- Published: 2026-02-20

---

**Use a singleton ConnectionPool with async/await, externalize credentials via `process.env`, and implement graceful shutdown handlers to ensure non-blocking, secure database access that aligns with Node.js core architectural patterns.**

Establishing a connection to a SQL Server database from a Node.js application using the **mssql** package requires architectural patterns that respect the runtime's single-threaded event loop. While the mssql driver operates outside the core `nodejs/node` repository, it leverages the internal `net`, `crypto`, and `process` modules to manage TCP sockets, TLS encryption, and environment configuration. Following the async, non-blocking principles defined in Node.js core ensures your database layer remains performant and resilient under production loads.

## Use Connection Pooling to Prevent Event Loop Blocking

Node.js runs JavaScript on a single-threaded event loop, meaning every synchronous network operation would freeze your application. The `mssql` package internally uses the **tedious** driver, which creates TCP sockets via the `net` module documented in [`doc/api/net.md`](https://github.com/nodejs/node/blob/main/doc/api/net.md). Opening a new socket for every query exhausts file descriptors and introduces latency.

**ConnectionPool** objects maintain a cache of reusable connections. Create one global pool at startup and reuse it for the entire process lifetime:

```javascript
import sql from 'mssql';
import { env } from 'process';

const config = {
  user: env.DB_USER,
  password: env.DB_PASSWORD,
  server: env.DB_SERVER,
  database: env.DB_NAME,
  pool: {
    max: 20,
    min: 5,
    idleTimeoutMillis: 30000,
  },
};

// Singleton pattern leverages module caching (see lib/internal/modules/cjs/loader.js)
let pool;

export async function getPool() {
  if (!pool) {
    pool = await new sql.ConnectionPool(config).connect();
  }
  return pool;
}

```

## Externalize Configuration Using Environment Variables

Hardcoding credentials violates security best practices and prevents deployment flexibility. Node.js exposes process environment variables through `process.env`, documented in [`doc/api/process.md`](https://github.com/nodejs/node/blob/main/doc/api/process.md). Store sensitive connection details outside your source code:

```javascript
const config = {
  user: env.DB_USER,
  password: env.DB_PASSWORD,
  server: env.DB_SERVER,
  options: {
    encrypt: env.DB_ENCRYPT === 'true', // Enforces TLS via crypto module
    trustServerCertificate: env.NODE_ENV === 'development',
  },
};

```

Reading configuration at runtime allows you to rotate secrets without redeploying code and aligns with the core runtime's handling of process environments.

## Implement Async/Await and Error Handling

Database I/O must never block the event loop. Node.js tracks asynchronous resources through mechanisms documented in [`doc/api/async_hooks.md`](https://github.com/nodejs/node/blob/main/doc/api/async_hooks.md). The mssql library returns Promises for all network operations, which you should consume with `async/await` syntax:

```javascript
export async function query(sqlText, params = {}) {
  const pool = await getPool();
  const request = pool.request();
  
  // Parameterized queries prevent injection
  for (const [name, value] of Object.entries(params)) {
    request.input(name, value);
  }
  
  try {
    const result = await request.query(sqlText);
    return result.recordset;
  } catch (err) {
    console.error('Query execution failed:', err);
    throw err; // Re-throw for upstream handling
  }
}

```

Wrap all database calls in `try/catch` blocks to prevent unhandled promise rejections from crashing the process.

## Ensure Graceful Shutdown

When your process receives a `SIGTERM` signal, pending database connections must close cleanly to prevent data corruption and event loop hanging. The Node.js process signal handling is documented in [`doc/api/process.md`](https://github.com/nodejs/node/blob/main/doc/api/process.md). Implement a shutdown hook that closes the ConnectionPool:

```javascript
export async function closePool() {
  if (pool) {
    await pool.close();
    pool = null;
  }
}

process.on('SIGTERM', async () => {
  console.log('SIGTERM received, closing database connections...');
  await closePool();
  process.exit(0);
});

```

This pattern ensures the `mssql` driver terminates TCP sockets properly using the underlying `net` module cleanup routines.

## Enable TLS Encryption for Production Traffic

SQL Server connections should encrypt data in transit using TLS, implemented via Node.js's `crypto` module ([`doc/api/crypto.md`](https://github.com/nodejs/node/blob/main/doc/api/crypto.md)). Set `encrypt: true` in your configuration to force TLS handshakes:

```javascript
const config = {
  options: {
    encrypt: true, // Requires crypto module support
    trustServerCertificate: false, // Always validate certs in production
  },
};

```

For self-signed certificates in development, you may set `trustServerCertificate: true`, but never enable this in production environments.

## Production-Ready Implementation Example

Combine these patterns into a centralized database module. The singleton export relies on Node.js's module caching behavior defined in [`lib/internal/modules/cjs/loader.js`](https://github.com/nodejs/node/blob/main/lib/internal/modules/cjs/loader.js) to maintain one global pool:

```javascript
// db.js
import sql from 'mssql';
import { env } from 'process';

const config = {
  user: env.DB_USER,
  password: env.DB_PASSWORD,
  server: env.DB_SERVER,
  database: env.DB_NAME,
  options: {
    encrypt: env.DB_ENCRYPT === 'true',
    trustServerCertificate: env.NODE_ENV !== 'production',
  },
  pool: {
    max: 20,
    min: 5,
    idleTimeoutMillis: 30000,
  },
};

let pool;

export async function getPool() {
  if (!pool) {
    pool = await new sql.ConnectionPool(config).connect();
    pool.on('error', err => {
      console.error('Unexpected pool error:', err);
    });
  }
  return pool;
}

export async function query(sqlText, params) {
  const p = await getPool();
  const request = p.request();
  
  if (params) {
    Object.entries(params).forEach(([key, value]) => {
      request.input(key, value);
    });
  }
  
  const result = await request.query(sqlText);
  return result.recordset;
}

export async function closePool() {
  if (pool) {
    await pool.close();
    pool = null;
  }
}

process.on('SIGTERM', closePool);
process.on('SIGINT', closePool);

```

Consume this module in your application entry point:

```javascript
// app.js
import { query } from './db.js';

async function fetchActiveUsers() {
  const users = await query(
    'SELECT id, name FROM Users WHERE active = @active',
    { active: true }
  );
  return users;
}

fetchActiveUsers().then(console.log).catch(console.error);

```

## Summary

- **Connection pooling** prevents event loop blocking by reusing TCP sockets managed by the `net` module.
- **`process.env`** externalization keeps credentials secure and follows [`doc/api/process.md`](https://github.com/nodejs/node/blob/main/doc/api/process.md) guidelines.
- **`async/await`** syntax maintains non-blocking I/O patterns tracked by the async_hooks system.
- **Graceful shutdown** handlers using `SIGTERM` listeners ensure clean pool closure via `pool.close()`.
- **TLS encryption** leverages the `crypto` module to secure data in transit when `encrypt: true` is set.

## Frequently Asked Questions

### What is the difference between ConnectionPool and individual connections?

**ConnectionPool** maintains a cache of active connections to SQL Server, allowing concurrent queries to reuse existing TCP sockets rather than creating new ones. Individual connections require manual management and tear-down, which violates Node.js best practices for resource efficiency. Always use `new sql.ConnectionPool(config)` and call `.connect()` once per application lifecycle.

### How do I handle connection errors in production?

Attach an `error` event listener to the pool instance to catch network failures and authentication issues. Additionally, wrap every `await pool.request()` call in `try/catch` blocks. For critical failures, implement circuit breaker patterns that temporarily halt queries when the error rate exceeds thresholds, preventing cascade failures in your Node.js application.

### Should I use environment variables for SQL Server credentials?

Yes. Store `user`, `password`, `server`, and database names in environment variables accessed via `process.env`. This aligns with Node.js security practices documented in [`doc/api/process.md`](https://github.com/nodejs/node/blob/main/doc/api/process.md) and allows you to inject different credentials via Docker secrets, Kubernetes config maps, or CI/CD pipelines without modifying source code.

### How do I enable TLS encryption for SQL Server connections?

Set `options: { encrypt: true }` in your mssql configuration object. This forces the driver to use TLS handshakes implemented by Node.js's `crypto` module before transmitting data. In production, always pair this with `trustServerCertificate: false` and provide valid CA certificates to prevent man-in-the-middle attacks.