mrkeyoor.com_
Sat 08 Aug 20:59 UTC
npmDataupdated 08 Aug 2026

tedious

Tedious is a low-level Node.js implementation of Microsoft's Tabular Data Stream protocol for SQL Server and Azure SQL. It opens a single database connection, authenticates with SQL credentials, NTLM, access tokens, service principals, managed identity, or Azure Identity, and exposes event-driven requests, typed parameters, stored procedures, transactions, prepared statements, bulk loads, result metadata, and row streaming. It deliberately does not provide connection pooling, an ORM, schema migrations, a query builder, or a Promise-first application API.

Verdict

Tedious is a capable foundation for SQL Server integrations that need protocol-level control, and it is actively maintained. Most application teams should install `mssql` instead for pooling and Promises, while watching the npm tag split before standardizing on version 20.

API stability3/5The Connection, Request, TYPES, row-event, parameter, transaction, stored-procedure, and bulk-load model has existed for years, and old SQL Server versions remain represented in the docs. Major upgrades still carry material platform changes: version 20 raises the minimum runtime to Node 22, and earlier releases changed automatic connection behavior, encryption defaults, Azure client requirements, aggregate errors, and nullable-column defaults.
Docs4/5The generated site has detailed Connection, Request, data type, bulk-load, authentication option, event, transaction, and FAQ references, plus repository examples for parameters, prepared statements, stored procedures, transactions, and bulk loads. It documents the one-request limit, encryption defaults, Azure requirements, Unicode conversion, numeric limits, and AggregateError handling. Some front-page examples still use var and CommonJS and feel older than version 20.
Maintenance4/5The repository was pushed on 2026-08-04, version 20.0.0 shipped on 2026-06-21, and five follow-up 20.0.x releases appeared by 2026-07-17 with fixes for login hangs, connection shutdown, cancellation, SNI, and parsing performance. GitHub reported 217 open issues and pull requests, a meaningful backlog. The unusual risk is release-channel coordination because npm `latest` still points to 20.0.0 while 20.0.5 is tagged `next`.
Ecosystem4/5Tedious underpins the popular `mssql` package and supports SQL Server 2000 through 2022 protocol versions plus Azure SQL authentication methods. npm recorded 4,532,260 downloads for 2026-07-31 through 2026-08-06, and the repository had 1,617 stars. Its integration reach is strong inside Node-to-SQL-Server projects, but it is intentionally database-specific and leaves pooling, migrations, ORM models, and Promise ergonomics to other packages.

Use it if

  • You need direct control over SQL Server's TDS protocol, request events, type metadata, stored procedures, table-valued parameters, or bulk loading
  • You are building a higher-level SQL Server adapter and want a pure-JavaScript transport instead of an ODBC native addon
  • You need Azure SQL authentication through DefaultAzureCredential, managed identity, a service principal, or an access token
  • Your application can provide its own pool and Promise wrapper while keeping all values in typed Request parameters
Skip it if

Setup reality

Tedious 20 starts with a hard platform gate: Node 22 or newer. Supply a hostname, database, and authentication block, then call `connection.connect()` explicitly; constructing a Connection no longer means it is ready. SQL Server must have TCP/IP enabled, the correct port reachable, and the login enabled. Encryption defaults to true. In production, keep `trustServerCertificate` false and install a certificate chain valid for the hostname; setting it true is a local-development concession, not a generic fix. Azure SQL users must choose the matching authentication type and supply tenant/client details or an environment that DefaultAzureCredential can resolve. SQL-password credentials belong in a secret store, never source. The driver is callback and event based: rows arrive one at a time, the completion callback is the safe point for the next request, and some failures are AggregateError values whose nested errors must be inspected. One connection handles only one request at once, so a web service needs a maintained pooling layer with checkout, timeout, health check, and guaranteed release logic. Do not use the old `tedious-connection-pool`; Tedious's FAQ calls it inactive. Always use `Request.addParameter()` with an explicit TYPES value rather than string interpolation, specify precision and scale for Decimal/Numeric, and choose NVarChar for Unicode. Row collection is off unless configured because retaining every row defeats streaming. Close connections on shutdown and after fatal errors, set connection/request timeouts, and test certificate, firewall, Azure token, cancellation, and failover behavior against the same SQL Server edition used in production.

Patterns

Connect with a SQL Server loginconnect-with-sql-login

import { Connection } from 'tedious';

const connection = new Connection({
  server: process.env.SQL_HOST!,
  authentication: {
    type: 'default',
    options: {
      userName: process.env.SQL_USER!,
      password: process.env.SQL_PASSWORD!,
    },
  },
  options: {
    database: process.env.SQL_DATABASE!,
    port: 1433,
    encrypt: true,
    trustServerCertificate: false,
    connectTimeout: 15_000,
  },
});

connection.connect((error) => {
  if (error) throw error;
  console.log('connected');
});

Version 20 requires Node 22. Keep certificate verification on in production; a hostname-valid SQL Server certificate avoids trustServerCertificate true.

Execute a query with typed parametersexecute-parameterized-query

import { Request, TYPES } from 'tedious';

const request = new Request(
  'SELECT id, name FROM dbo.Users WHERE email = @email',
  (error, rowCount) => {
    if (error) throw error;
    console.log({ rowCount });
  },
);
request.addParameter('email', TYPES.NVarChar, email, { length: 320 });
connection.execSql(request);

Parameter names omit the `@`. Use NVarChar for Unicode and set realistic lengths so SQL Server can choose better plans.

Collect row events into objectscollect-query-rows

const rows: Record<string, unknown>[] = [];
const request = new Request(sql, (error) => {
  if (error) return reject(error);
  resolve(rows);
});
request.on('row', (columns) => {
  rows.push(Object.fromEntries(
    columns.map((column) => [column.metadata.colName, column.value]),
  ));
});
connection.execSql(request);

This buffers every row in application memory. For large results, process row events incrementally and apply pause/resume backpressure.

Pause a request while processing a rowstream-rows-with-backpressure

request.on('row', (columns) => {
  request.pause();
  void persistRow(columns)
    .then(() => request.resume())
    .catch((error) => {
      request.cancel();
      report(error);
    });
});
connection.execSql(request);

Do not start another request while this one is paused. One Tedious connection can execute only one request at a time.

Set and handle a request timeoutset-request-timeout

const request = new Request(sql, (error) => {
  if (error) {
    console.error(error);
    return;
  }
});
request.setTimeout(5_000);
connection.execSql(request);

Timeout triggers cancellation, which is not the same as rolling back a wider transaction. Test the pinned 20.x release because later next-tag builds contain cancellation fixes.

Commit or roll back a transactionrun-transaction

connection.beginTransaction((beginError) => {
  if (beginError) throw beginError;

  const request = new Request(sql, (requestError) => {
    if (requestError) {
      return connection.rollbackTransaction((rollbackError) => {
        if (rollbackError) throw rollbackError;
      });
    }
    connection.commitTransaction((commitError) => {
      if (commitError) throw commitError;
    });
  });
  request.addParameter('amount', TYPES.Decimal, amount, { precision: 18, scale: 2 });
  connection.execSql(request);
});

Wait for each callback before the next operation. If rollback also fails, preserve both errors and discard the connection from the pool.

Prepare and execute a repeated statementprepare-and-execute

const request = new Request(
  'INSERT INTO dbo.Events (kind, payload) VALUES (@kind, @payload)',
  (error) => { if (error) throw error; },
);
request.addParameter('kind', TYPES.NVarChar);
request.addParameter('payload', TYPES.NVarChar);
request.on('prepared', () => {
  request.once('requestCompleted', () => connection.unprepare(request));
  connection.execute(request, { kind: 'login', payload: '{}' });
});
connection.prepare(request);

Always unprepare when finished to release the server handle. A prepared Request occupies the connection while its operations run.

Call a procedure with an output parametercall-stored-procedure

const request = new Request('dbo.CountCharacters', (error) => {
  if (error) throw error;
});
request.addParameter('inputVal', TYPES.VarChar, 'hello');
request.addOutputParameter('outputCount', TYPES.Int);
request.on('returnValue', (name, value) => {
  console.log(name, value);
});
connection.callProcedure(request);

The Request text is the procedure name for callProcedure, not a CALL or EXEC SQL string. Output values arrive through returnValue events.

Bulk-load validated rowsbulk-insert-rows

const bulk = connection.newBulkLoad(
  'dbo.ImportRows',
  { keepNulls: true },
  (error, rowCount) => {
    if (error) throw error;
    console.log({ rowCount });
  },
);
bulk.addColumn('id', TYPES.Int, { nullable: false });
bulk.addColumn('name', TYPES.NVarChar, { length: 100, nullable: true });

connection.execBulkLoad(bulk, [
  { id: 1, name: 'Ada' },
  { id: 2, name: null },
]);

Column order, names, types, lengths, and nullability must match the target table. Validate and batch large inputs before sending them.

Use the Azure default credential chainconnect-with-azure-identity

const connection = new Connection({
  server: 'example.database.windows.net',
  authentication: {
    type: 'azure-active-directory-default',
    options: {},
  },
  options: {
    database: 'app',
    encrypt: true,
    trustServerCertificate: false,
  },
});

DefaultAzureCredential can try several sources. Constrain and test the deployed identity setup so local developer credentials do not hide missing production configuration.

Log every nested driver errorinspect-aggregate-errors

function reportTediousError(error: unknown) {
  if (error instanceof AggregateError) {
    for (const cause of error.errors) console.error(cause);
    return;
  }
  console.error(error);
}

Some authentication and request paths return AggregateError so the original SQL Server or token error is not lost behind a generic failure.

Close a connection on shutdownclose-connection

process.once('SIGTERM', () => {
  connection.once('end', () => process.exit(0));
  connection.close();
});

In a pooled service, stop accepting work first, wait for checked-out requests, then drain the pool rather than closing one arbitrary connection.

Alternatives

PackageRegistryPick it when
mssqlnpmChoose it for a Promise-first SQL Server client with pooling that uses Tedious as its default driver
msnodesqlv8npmChoose it when native ODBC performance or Windows trusted connections justify platform-specific binaries
sequelizenpmChoose it when models, associations, migrations, and cross-database ORM behavior matter more than TDS-level control
knexnpmChoose it for a query builder, migrations, and pooling while retaining more SQL visibility than an ORM