How to Connect to SQL Server from Node.js
The standard way to reach Microsoft SQL Server from Node.js is the mssql package, a higher-level wrapper over the tedious driver. tedious is the pure-JavaScript implementation of SQL Server's TDS protocol; mssql adds connection pooling, a promise API, and prepared statements on top. Use mssql unless you have a specific reason to drop down to raw Tedious. Current versions are mssql 12.5.5 and tedious 19.2.1 (as of June 2026).
npm install mssqlThe encrypt + trustServerCertificate trap
This is the single most common reason a first connection fails. Since mssql 8, encryption is on by default. Against a local SQL Server or a container with a self-signed certificate, the driver then rejects the certificate and the connection dies with a TLS error -- even though your username and password are correct.
For local development against a self-signed cert, you accept the certificate explicitly:
import sql from "mssql";
const pool = await sql.connect({
server: "localhost",
port: 1433,
user: "sa",
password: process.env.MSSQL_PASSWORD,
database: "analytics",
options: {
encrypt: true,
trustServerCertificate: true, // local/self-signed only
},
});In production against Azure SQL or a server with a real certificate, keep encrypt: true and leave trustServerCertificate at its default of false so the certificate is actually verified. Setting it to true in production gives you an encrypted-but-unauthenticated channel, which defeats the point.
Use the pool, query with parameters
sql.connect() returns a global pool. For most apps that single pool is what you want -- create it once at startup and reuse it, rather than connecting per request. Use the request().input() API to bind parameters; never string-concatenate values into the SQL:
const result = await pool.request()
.input("active", sql.Bit, true)
.query("SELECT id, email FROM users WHERE active = @active");
console.log(result.recordset);result.recordset is the array of rows. For statements that return multiple result sets, they arrive in result.recordsets (note the plural). Typing the input with sql.Bit, sql.Int, sql.NVarChar, etc. avoids implicit-conversion surprises on the server side.
Managing multiple pools
If you connect to more than one database, do not call sql.connect() repeatedly -- it manages a single global pool and the second call returns the first connection. Instead create explicit pools:
const reporting = new sql.ConnectionPool(reportingConfig);
await reporting.connect();
const rows = await reporting.request().query("SELECT * FROM daily_totals");Call pool.close() on shutdown.
Azure AD authentication
For Azure SQL Database, password auth is often disabled in favor of Entra ID (Azure AD). The mssql config takes an authentication block:
const pool = await sql.connect({
server: "myserver.database.windows.net",
database: "analytics",
options: { encrypt: true },
authentication: {
type: "azure-active-directory-default",
options: { /* picks up managed identity or az login */ },
},
});The azure-active-directory-default type uses the same credential chain as the Azure SDK (managed identity in production, az login locally), so you avoid storing secrets at all.
The named instance and port question
If you connect to a named instance (SERVER\\SQLEXPRESS) rather than a port, the driver needs the SQL Server Browser service running to resolve the instance to a dynamic port. That service is often firewalled off. When a named-instance connection times out, the simplest fix is to find the instance's actual TCP port and connect to server + port directly instead of using the instance name.
Verifying the connection
const result = await pool.request().query("SELECT @@VERSION AS version");
console.log(result.recordset[0].version);
await pool.close();To browse SQL Server tables and run ad-hoc queries without writing a connection script first, Mako connects to SQL Server with AI-powered autocomplete. Try it free at mako.ai.
Skip the terminal. Use Mako.
Connect your database, write queries with AI assistance, and import/export data in clicks. Free to start.