mssql and tedious are both npm packages used to interact with Microsoft SQL Server databases from Node.js applications. tedious is a low-level, pure-JavaScript implementation of the TDS (Tabular Data Stream) protocol used by SQL Server. It provides direct access to the wire protocol but requires more boilerplate for common tasks. mssql, on the other hand, is a higher-level abstraction built on top of drivers like tedious (and others), offering a cleaner, promise-based API with connection pooling, transaction support, and simplified query execution.
When your Node.js app needs to talk to Microsoft SQL Server, you’ll likely encounter two packages: mssql and tedious. While both ultimately connect to SQL Server, they serve very different roles in the stack. Let’s cut through the confusion.
mssql is a database driver abstraction layer. It doesn’t implement the SQL Server protocol itself — instead, it uses underlying drivers like tedious (for pure JavaScript) or msnodesqlv8 (for native ODBC). This lets you write clean, consistent code regardless of the backend driver.
// mssql: Simple, high-level usage
const sql = require('mssql');
await sql.connect('server=localhost;database=mydb;user=sa;password=...');
const result = await sql.query`SELECT * FROM Users WHERE id = ${userId}`;
console.log(result.recordset);
tedious is a pure-JavaScript implementation of the TDS protocol. It handles the raw byte-level communication with SQL Server but gives you no convenience methods for queries, transactions, or connection reuse.
// tedious: Low-level, verbose usage
const { Connection, Request } = require('tedious');
const config = { server: 'localhost', authentication: { ... }, options: { database: 'mydb' } };
const connection = new Connection(config);
connection.on('connect', (err) => {
if (err) throw err;
const request = new Request('SELECT * FROM Users WHERE id = @id', (err, rowCount) => {
if (err) console.error(err);
});
request.addParameter('id', TYPES.Int, userId);
request.on('row', (columns) => {
console.log(columns.map(col => col.value));
});
connection.execSql(request);
});
connection.connect();
💡 Key insight:
mssqlcan actually usetediousunder the hood. Runnpm install mssql tediousandmssqlwill automatically picktediousas its driver.
mssql includes built-in connection pooling. You call connect() once, and it reuses connections behind the scenes. No need to manage open/closed states per query.
// mssql: Automatic pooling
const pool = new sql.ConnectionPool(config);
await pool.connect();
// Each query borrows a connection from the pool
const req1 = pool.request().query('SELECT 1');
const req2 = pool.request().query('SELECT 2');
tedious has no connection pooling. Every time you need a query, you must either reuse a single connection (risky under load) or manually implement pooling logic.
// tedious: No pooling — you’re on your own
const conn1 = new Connection(config1);
const conn2 = new Connection(config2);
// ... manage lifecycle, error recovery, concurrency limits yourself
If your app handles concurrent requests, rolling your own pool with tedious quickly becomes error-prone and complex.
mssql uses modern async/await syntax. Queries return promises, making code linear and easy to read.
// mssql: Clean async flow
try {
const result = await pool.request()
.input('email', sql.VarChar, 'user@example.com')
.query('SELECT * FROM Users WHERE email = @email');
return result.recordset;
} catch (err) {
console.error('Query failed:', err);
}
tedious relies on Node.js event emitters. You attach .on('row'), .on('done'), and .on('error') handlers, which leads to fragmented logic.
// tedious: Callback-heavy
const request = new Request('SELECT * FROM Users WHERE email = @email', (err) => {
if (err) console.error(err);
});
request.addParameter('email', TYPES.VarChar, 'user@example.com');
let rows = [];
request.on('row', (columns) => {
rows.push(columns.map(c => c.value));
});
request.on('done', () => {
console.log('Results:', rows);
});
connection.execSql(request);
This pattern doesn’t play well with async/await unless you wrap it in a Promise yourself.
mssql makes transactions trivial:
// mssql: Built-in transaction support
const transaction = new sql.Transaction(pool);
await transaction.begin();
try {
await transaction.request().query('INSERT INTO Logs ...');
await transaction.request().query('UPDATE Accounts ...');
await transaction.commit();
} catch (err) {
await transaction.rollback();
throw err;
}
With tedious, you must manually execute BEGIN TRANSACTION, COMMIT, and ROLLBACK as SQL strings and track state yourself.
// tedious: Manual transaction control
const beginReq = new Request('BEGIN TRANSACTION', (err) => { /* ... */ });
connection.execSql(beginReq);
// Then run your queries...
// Then run 'COMMIT' or 'ROLLBACK' based on success/failure
Similarly, calling stored procedures in mssql is straightforward:
// mssql
await pool.request()
.input('param', value)
.execute('MyStoredProcedure');
In tedious, you use execProc() but still manage parameters and events manually.
Don’t use tedious for standard application data access. Its low-level nature introduces unnecessary complexity for 95% of use cases. Only consider it if you’re building a database proxy, custom ORM, or need to inspect TDS packets.
Don’t assume mssql is “slower.” The abstraction overhead is negligible compared to network latency. In fact, its built-in pooling often makes it faster in real-world apps than naive tedious usage.
Despite their differences, both packages:
// Both support secure connections
const config = {
server: 'prod-sql',
options: { encrypt: true }, // enables TLS
authentication: { type: 'default', options: { userName, password } }
};
Date ↔ DATETIME2)VARCHAR(MAX), VARBINARY)// mssql
.input('bio', sql.NVarChar(sql.MAX), longText)
// tedious
request.addParameter('bio', TYPES.NVarChar, longText, { length: 'MAX' });
| Feature | mssql | tedious |
|---|---|---|
| Abstraction Level | High-level ORM-like API | Raw TDS protocol implementation |
| Connection Pooling | ✅ Built-in | ❌ Manual only |
| Async Style | ✅ Promises / async-await | ❌ Event emitters |
| Transactions | ✅ First-class support | ❌ Manual SQL commands |
| Learning Curve | Gentle — familiar to SQL users | Steep — requires protocol knowledge |
| Use Case | Application data access | Database tooling / custom drivers |
For frontend developers integrating with SQL Server backends (or full-stack devs building Node.js APIs), use mssql. It removes boilerplate, prevents common pitfalls like connection leaks, and lets you focus on business logic.
Reserve tedious for specialized scenarios where you need to manipulate the TDS stream directly — like building a query analyzer, custom connection router, or educational tool. In day-to-day app development, it’s almost always the wrong choice.
Remember: mssql + tedious is a common combo — install both, and let mssql handle the ergonomics while tedious does the protocol work quietly underneath.
Choose tedious only if you need fine-grained control over the TDS protocol, are building a database tool or proxy, or must avoid abstractions for performance or compatibility reasons. Be prepared to handle connection lifecycle, request queuing, and error recovery manually — it’s not suited for routine CRUD operations in standard apps.
Choose mssql if you want a developer-friendly, high-level interface for SQL Server with features like automatic connection pooling, promise/async-await support, and built-in transaction handling. It’s ideal for typical application development where you need to run queries without managing low-level protocol details or manual resource cleanup.
Tedious is a pure-Javascript implementation of the TDS protocol, which is used to interact with instances of Microsoft's SQL Server. It is intended to be a fairly slim implementation of the protocol, with not too much additional functionality.
NOTE: New columns are nullable by default as of version 1.11.0
Previous behavior can be restored using config.options.enableAnsiNullDefault = false. See pull request 230.
NOTE: Default login behavior has changed slightly as of version 1.2
See the changelog for version history.
Node.js is a prerequisite for installing tedious. Once you have installed Node.js, installing tedious is simple:
npm install tedious
More documentation and code samples are available at tediousjs.github.io/tedious/
Tedious is simply derived from a fast, slightly garbled, pronunciation of the letters T, D and S.
We'd like to learn more about how you use tedious:
We welcome contributions from the community. Feel free to checkout the code and submit pull requests.
Copyright (c) 2010-2021 Mike D Pilsbury
The MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.