Link to Why Migrate from ldapjs to ldapts?Why Migrate from ldapjs to ldapts?
The ldapjs library—a mainstay for LDAP client integrations with Node.js—has been officially deprecated and is no longer maintained. This status exposes any codebase depending on ldapjs to accumulating security risks and potential breakage as Node.js itself moves forward. In contrast, ldapts is actively maintained, built in TypeScript, and designed for modern development patterns. It offers a promise-based, async/await API, and brings strict type safety, reducing the incidence of runtime errors and making integrations more robust and maintainable. For any team maintaining or developing LDAP directory integration in Node.js, migration to ldapts is now a baseline requirement for long-term supportability and security.
Link to Key Differences Between ldapjs and ldapts Search APIsKey Differences Between ldapjs and ldapts Search APIs
The primary architectural change when moving from ldapjs to ldapts affects how search operations are written and how their results and errors are handled:
Event/Callback vs Async/Await Promise
ldapjs search returns a result object that emits events (searchEntry,error,end). Processing results means registering listeners for each event, and error/control flow is distributed across callbacks and event handlers.
ldapts uses a completely promise-based API. Thesearchmethod returns a promise resolving to an object containing{ searchEntries, searchReferences }. Results are returned as an array, simplifying consumption and enabling idiomatic async/await patterns.Centralized Error Handling
In ldapjs, errors may occur in the callback (such as connection failures) or be emitted on the result (for search errors), requiring diligence across multiple places.
ldapts surfaces all operational errors as rejections of the search promise, so errors can be managed centrally with try/catch, improving clarity and reliability.TypeScript and Type Rigor
ldapts defines strict TypeScript types for all search options and results. This exposes misconfigurations at compile time, increasing code correctness. ldapjs, being JavaScript-first and loosely typed, can let incorrect usage or option structures pass silently, only to error at runtime.API Signatures
- ldapjs:
client.search(baseDN, options, callback)
Processes results and errors via event listeners on the result stream. - ldapts:
client.search(baseDN, options)
Returns aPromise<{ searchEntries, searchReferences }>; entries can be processed with ordinary iteration.
- ldapjs:
While both libraries use standards-based LDAP filter strings and share similar option field names, the mechanics for handling results and errors are fundamentally changed.
Link to Mapping ldapjs Search Code to ldaptsMapping ldapjs Search Code to ldapts
Migrating from ldapjs’s event/callback approach to ldapts’s promise-based design means reworking control flow, result processing, and error handling. Below is a step-by-step translation for a canonical LDAP search, encompassing connection, binding, searching, error processing, and proper unbinding.
Link to Example: Simple Search MigrationExample: Simple Search Migration
ldapjs before:
const ldap = require('ldapjs');
const client = ldap.createClient({ url: 'ldap://ldap.example.com' });
client.bind('cn=admin,dc=example,dc=com', 'password', (err) => {
if (err) throw err;
client.search('ou=users,dc=example,dc=com', { scope: 'sub', filter: '(uid=john)' }, (err, res) => {
if (err) throw err;
res.on('searchEntry', (entry) => {
console.log('Entry:', entry.object);
});
res.on('error', (err) => {
console.error('Search error:', err);
});
res.on('end', () => {
client.unbind();
});
});
});
ldapts after:
import { Client } from 'ldapts';
const client = new Client({ url: 'ldap://ldap.example.com' });
try {
await client.bind('cn=admin,dc=example,dc=com', 'password');
const { searchEntries } = await client.search('ou=users,dc=example,dc=com', {
scope: 'sub',
filter: '(uid=john)',
});
for (const entry of searchEntries) {
console.log('Entry:', entry);
}
} catch (err) {
console.error('LDAP error:', err);
} finally {
await client.unbind();
}
Link to Code Mapping TableCode Mapping Table
| Task | ldapjs | ldapts |
|---|---|---|
| Import/create client | require('ldapjs')<br>ldap.createClient | import { Client } from 'ldapts'<br>new Client |
| Connect & bind | client.bind(..., (err) => ...) | await client.bind(...) inside try |
| Run search | client.search(..., (err, res) => ...) | await client.search(...) returns Promise |
| Handle entries | res.on('searchEntry', fn) | Iterate over searchEntries array |
| Handle errors | res.on('error', fn)<br>callback errors | catch block (or .catch) for all errors |
| End/unbind | res.on('end', fn) + client.unbind() | Always call await client.unbind() in finally |
| Search options | { scope, filter, ... } | { scope, filter, ... } (typed, similar) |
Link to Notable Behavioral ShiftsNotable Behavioral Shifts
- Result Iteration: ldapjs streams entries via events (
searchEntry). In ldapts, the entire result set is available as an array, simplifying processing. - Error Flow: ldapts centralizes all errors into one flow, reducing the possibility of missed error handlers.
- Unbinding: In ldapts, placing
unbindin a finally block ensures the connection is always closed, regardless of errors.
Link to Migrating Paginated SearchMigrating Paginated Search
In ldapjs, paginated searches are typically managed using controls and by maintaining state across events—this can be complex and brittle. ldapts offers a dedicated searchPaginated method that streamlines pagination.
ldapjs (event-driven paging):
client.search(baseDN, {
filter: '(objectClass=person)',
scope: 'sub',
paged: { pageSize: 100 }
}, (err, res) => {
res.on('searchEntry', (entry) => { ... });
// Manual pagination state management...
});
ldapts (built-in pagination):
const { searchEntries } = await client.searchPaginated(baseDN, {
filter: '(objectClass=person)',
scope: 'sub',
pageSize: 100,
});
// searchEntries contains all results from all pages
Paging complexities—such as managing control cookies and completion—are handled internally within ldapts. This eliminates manual tracking and reduces the likelihood of subtle paging bugs.
Link to Pitfalls, Common Issues, and MisconceptionsPitfalls, Common Issues, and Misconceptions
ldapts is not a drop-in replacement
Migration to ldapts requires a true code rewrite, not just a package name swap. Any ldapjs code that relies on event emitters or callback patterns must be refactored to promises and async/await, or it will immediately break.LDAP search filters remain the same
The syntax of LDAP filters like(uid=john)or(objectClass=person)is standardized (per RFC 4515) and does not change between ldapjs and ldapts. Only the API for passing these filters is different.Error handling is now centralized
ldapts gathers all errors—including connection, bind, search, and unbind operations—into promise rejections or exceptions. Neglecting to handle errors in a unified place can result in missed exceptions, so adjust your code accordingly.Pagination is now first-class
If your ldapjs code includes custom paging logic or listens for events to manage pagination, replace this withsearchPaginatedin ldapts. Paging state is managed by the library itself, and all results are returned in a single concatenated array when used with default options.TypeScript reveals hidden bugs
The stricter typing of ldapts will often surface misuses of search options or filter structures that went unnoticed in ldapjs due to loose typing. Fixing these may require minor option corrections post-migration.
Link to Testing, Validation, and Further Migration ConsiderationsTesting, Validation, and Further Migration Considerations
Thorough testing is essential to validate that your migration preserves all key behaviors:
Functional Equivalence
Test all important search cases before and after migration. Confirm that searches return the expected set of entries, and that filter and attribute handling mirror original code.Error-Flow Coverage
Intentionally trigger known error conditions: wrong credentials, invalid search bases and filters, or insufficient permissions. Every path that produced an error in ldapjs should yield a clear exception in ldapts.Pagination Validation
If your application relies on paginated results, validate thatsearchPaginatedreturns all expected entries. Compare result counts and contents across old and new code.TypeScript Build Checks
For TypeScript applications, let the compiler reveal any mistakes in option fields, expected values, or handling of result shapes. This often exposes hidden bugs not visible in loosely typed ldapjs code.Caution—Defaults May Differ
Some search option defaults (attributes,sizeLimit,scope, and others) may not match exactly between ldapjs and ldapts. Always review and compare options explicitly for each operation. Check the ldapts API documentation for all option fields to ensure parity. Assume that relying on implicit defaults risks subtle mismatches in search results or performance.
Link to Authoritative Resources and Further ReadingAuthoritative Resources and Further Reading
Official project repositories and documentation provide the most up-to-date details and method references for advanced migration cases. Consult them directly when translating complex search operations, binding methods, or when troubleshooting differences in behavior between ldapjs and ldapts.