tedious

A pure-JavaScript TDS driver that lets Node.js applications talk directly to Microsoft SQL Server.

Library
npm
v20.0.0
1,617stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
92/100Excellent
Development Activity96
Maintenance96
Community88
Maturity60
Momentum28

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
78/100Good
Architecture85
Code Quality88
Innovation75
Learning Curve65

Tedious is a pure-JavaScript implementation of Microsoft’s Tabular Data Stream (TDS) protocol, giving Node.js applications a direct wire-level client for SQL Server without any native bindings. It handles the full connection lifecycle — TCP/TLS negotiation, SQL Server instance discovery via SQL Browser, NTLM and Azure Active Directory (including Managed Identity and Default Azure Credential) authentication, and TDS version negotiation back to SQL Server 2000.

Beyond basic query execution, Tedious exposes SQL Server-specific capabilities that generic SQL clients don’t: bulk insert via BulkLoad, table-valued parameters, stored procedure calls with typed parameters across more than 30 native TDS column types, and Always Encrypted column-level encryption backed by Azure Key Vault. Its API is deliberately low-level and event-driven — Connection and Request objects emit events for rows, column metadata, and completion — which keeps it close to the protocol and makes it a common foundation underneath higher-level libraries such as mssql and Sequelize’s SQL Server dialect, rather than a query-builder in its own right.

The project has been maintained since 2011 and is actively developed, with CI running its unit and integration suites against real SQL Server instances in Docker across multiple Node.js and TDS versions.

What You Get

  • A Connection class handling TCP/TLS setup, SQL Browser instance lookup, and TDS version negotiation down to SQL Server 2000
  • NTLM and Azure AD authentication, including Managed Identity, Default Azure Credential, and client-secret service principal flows
  • BulkLoad for high-throughput bulk inserts and native support for table-valued parameters and stored procedure calls
  • Always Encrypted column encryption with Azure Key Vault-backed key unwrapping for querying encrypted columns without exposing plaintext to the server
  • Native support for 30+ TDS data types (decimal, numeric, datetime2, datetimeoffset, xml, udt, sql_variant, and more) with precise wire-format encoding/decoding
  • Event-driven Request objects streaming columnMetadata, row, and done events for full control over result processing

Common Use Cases

  • Connecting a Node.js backend directly to an on-prem or Azure SQL Server database without native/ODBC drivers
  • Serving as the underlying driver for a higher-level ORM or query builder that targets SQL Server (as mssql and Sequelize’s SQL Server dialect do)
  • Bulk-loading large datasets into SQL Server tables via BulkLoad instead of row-by-row inserts
  • Authenticating to Azure SQL Database using Managed Identity or Azure AD credentials instead of SQL logins
  • Querying and writing Always Encrypted columns from a Node.js service while keeping sensitive data encrypted at rest and in transit

Under The Hood

Architecture Tedious is organized around a Connection class that drives a protocol state machine — prelogin, then NTLM or Azure AD login (login7-payload.ts, ntlm-payload.ts), followed by token-stream parsing and request execution — implemented across a clear separation between transport (connector.ts, message-io.ts, packet.ts), protocol encoding (login7-payload.ts, prelogin-payload.ts, rpcrequest-payload.ts, sqlbatch-payload.ts, bulk-load-payload.ts), data marshaling (data-type.ts plus 30+ files under data-types/), and column-encryption support (always-encrypted/). Incoming TDS token streams are parsed by a dedicated token-stream-parser dispatching to per-phase handler classes (Login7TokenHandler, RequestTokenHandler, AttentionTokenHandler). This layering is clean, but the handler classes and payload builders are tightly coupled to the Connection lifecycle, so changes to the core state machine would ripple across most of the protocol-encoding layer.

Tech Stack Written in TypeScript but compiled via Babel (@babel/preset-typescript and @babel/preset-env targeting Node 22) rather than tsc, with tsc used separately only to emit type declarations (tsconfig.build-types.json). Runtime dependencies are narrow and purpose-built: @azure/identity, @azure/core-auth, and @azure/keyvault-keys for Azure AD and Always Encrypted key-unwrapping, @js-joda/core for temporal value handling, bl for buffer-list stream assembly, and iconv-lite for character-encoding conversions. The published package ships both compiled JS (lib/) and generated .d.ts type declarations.

Code Quality The test suite is extensive: a unit suite under test/unit covering nearly every module (connection handling, bulk load, each token/payload type) using Mocha, Chai, and Sinon with nyc coverage, plus a separate integration suite that runs against a real SQL Server instance in Docker (test/docker-compose.linux.yml) across multiple TDS and Node.js versions in CI. Errors use typed subclasses (ConnectionError, RequestError, InputError) carrying protocol-specific fields like number, state, and serverName rather than generic Error objects. Linting combines ESLint’s flat config with a tsc type-check gate, and CodeQL static analysis runs in CI alongside PR-title linting.

API Design The public API is intentionally low-level and event-driven rather than promise-based — Connection and Request objects emit columnMetadata, row, done, and error events, mirroring the underlying protocol closely instead of hiding it behind a query builder. This gives direct access to SQL Server-specific behavior (bulk loads, table-valued parameters, Always Encrypted) that a generic SQL client wouldn’t expose, at the cost of more boilerplate for callers than a modern promise/async-iterator client would require — which is consistent with its role as the foundation underneath higher-level libraries rather than an end-user-facing client itself.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search