devlock-sdk
v1.0.8
Published
DevLock Backend SDK for Node.js, Express, Fastify, and NestJS. Protect your APIs with real-time license validation, remote kill-switch, API suspension, and distributed caching features.
Downloads
279
Maintainers
Readme
devlock-sdk
Backend SDK for DevLock — License enforcement, remote management, and developer protection for Node.js applications.
🌐 Website & Dashboard: devlock.tashanto.com · 🌍 Frontend SDK: devlock-client
Fail-safe by design. If DevLock's servers are ever unreachable,
init()never throws and the middleware never blocks traffic on our outage — your service keeps serving requests. See Fail-open behavior.
Features
- 🔐 License Validation — HMAC-signed server-to-server verification with caching
- ⚡ Kill Switch — Instant application disable via WebSocket
- 🔧 Maintenance Mode — Return 503 automatically when enabled
- 🏴 Feature Flags — Real-time feature toggling
- 📡 Offline Support — Redis + in-memory cache with grace period
- 🔄 Auto-Sync — Periodic config sync with WebSocket push updates
- 📊 Telemetry — Batched event tracking
- 🛡️ API Suspension — Remote API disable capability
- 🔁 Retry Logic — Exponential backoff with jitter
Framework Support
| Framework | Import Path |
|-----------|-------------|
| Core (any Node.js) | devlock-sdk |
| Express.js | devlock-sdk/express |
| Fastify | devlock-sdk/fastify |
| NestJS | devlock-sdk/nestjs |
Installation
npm install devlock-sdk
# or
yarn add devlock-sdk
# or
pnpm add devlock-sdkSetup & Dashboard
Before using the SDK, you need to create a project and obtain your keys from the DevLock Dashboard:
- Go to DevLock Dashboard.
- Sign in and navigate to the Projects section.
- Click Create Project and fill in your details.
- Copy your Project Public Key (
pk_live_...) and Secret Key (sk_live_...).
⚠️ Key Naming: The
projectIdfield in the SDK config is your Public Key (pk_live_...), not a separate UUID. The Secret Key (sk_live_...) must be kept secure and never exposed to the frontend.
Recommended .env setup
DEVLOCK_PROJECT_ID=pk_live_your_public_key_here
DEVLOCK_SECRET_KEY=sk_live_your_secret_key_here
DEVLOCK_LICENSE_KEY=DLCK-XXXX-XXXX-XXXX-XXXXQuick Start
Express.js (Recommended)
import express from 'express';
import { createMiddleware } from 'devlock-sdk/express';
const app = express();
app.use(createMiddleware({
secretKey: process.env.DEVLOCK_SECRET_KEY!,
projectId: process.env.DEVLOCK_PROJECT_ID!, // ← your pk_live_... key
excludePaths: ['/health', '/public', '/webhooks'],
failBehavior: 'open', // recommended — never block traffic on DevLock outage
on: {
onReady: () => {
console.log('DevLock license protection active');
},
onKillSwitch: (reason) => {
console.error('Kill switch activated:', reason);
},
onMaintenance: (enabled, message) => {
console.log('Maintenance:', enabled, message);
},
},
}));
// Access license info in route handlers
app.get('/api/data', (req, res) => {
const { license, isFeatureEnabled, track } = req.devlock!;
if (isFeatureEnabled('advanced-export')) {
// serve premium feature
}
track('data_accessed', { endpoint: '/api/data' });
res.json({ features: license.features, status: license.status });
});
app.listen(3000);Single-App License (No per-user license keys)
If your app has one license for the whole application (not per-user SaaS), use extractLicenseKey to always return your app's license key:
app.use(createMiddleware({
secretKey: process.env.DEVLOCK_SECRET_KEY!,
projectId: process.env.DEVLOCK_PROJECT_ID!,
failBehavior: 'open',
// Return the app's own license key for every request
extractLicenseKey: (_req) => process.env.DEVLOCK_LICENSE_KEY,
excludePaths: ['/health', '/auth/login'],
}));This pattern is ideal for:
- Internal tools / admin dashboards
- B2B SaaS apps sold as a single deployment
- Desktop apps built with Electron
Fastify
import Fastify from 'fastify';
import { devlockPlugin } from 'devlock-sdk/fastify';
const app = Fastify();
await app.register(devlockPlugin, {
secretKey: process.env.DEVLOCK_SECRET_KEY!,
projectId: process.env.DEVLOCK_PROJECT_ID!,
excludePaths: ['/health'],
});
app.get('/api/data', (request, reply) => {
const { license, isFeatureEnabled } = request.devlock;
reply.send({ features: license.features });
});
await app.listen({ port: 3000 });NestJS
// app.module.ts
import { Module } from '@nestjs/common';
import { DevLockModule } from 'devlock-sdk/nestjs';
@Module({
imports: [
DevLockModule.forRoot({
secretKey: process.env.DEVLOCK_SECRET_KEY!,
projectId: process.env.DEVLOCK_PROJECT_ID!,
excludePaths: ['/health'],
}),
],
})
export class AppModule {}Core (Framework-Agnostic)
import { DevLock } from 'devlock-sdk';
const devlock = new DevLock({
secretKey: process.env.DEVLOCK_SECRET_KEY!,
projectId: process.env.DEVLOCK_PROJECT_ID!,
on: {
onKillSwitch: (reason) => console.error('Disabled:', reason),
onMaintenance: (enabled) => console.log('Maintenance:', enabled),
},
});
await devlock.init();
// Validate a license
const result = await devlock.validateLicense('DLCK-XXXX-XXXX-XXXX-XXXX');
console.log(result.valid, result.features);
// Check state
if (devlock.isMaintenanceMode()) { /* ... */ }
if (devlock.isKillSwitchActive()) { /* ... */ }
if (devlock.isFeatureEnabled('premium')) { /* ... */ }
// Track events
devlock.track({ type: 'api_call', timestamp: Date.now(), path: '/users' });
// Cleanup
devlock.destroy();Configuration
const devlock = new DevLock({
// Required
secretKey: 'sk_live_xxx', // Project secret key (keep server-side only)
projectId: 'pk_live_xxx', // Project public key (from Dashboard → Projects)
// Optional
apiUrl: 'https://dl-api.tashanto.com', // Custom API URL
wsUrl: 'wss://dl-ws.tashanto.com', // Custom WebSocket URL
environment: 'production', // 'production' | 'staging' | 'development'
syncInterval: 300000, // Config sync interval (ms, default: 5min)
cacheTtl: 300000, // License cache TTL (ms, default: 5min)
offlineGraceHours: 72, // Offline grace period (hours)
realtime: true, // Enable WebSocket updates
failBehavior: 'open', // 'open' (default) | 'closed'
redis: redisClient, // Optional Redis for distributed caching
logger: customLogger, // Custom logger (default: console)
});Redis Caching (Recommended for Production)
import Redis from 'ioredis';
import { createMiddleware } from 'devlock-sdk/express';
const redis = new Redis(process.env.REDIS_URL);
app.use(createMiddleware({
secretKey: process.env.DEVLOCK_SECRET_KEY!,
projectId: process.env.DEVLOCK_PROJECT_ID!,
redis: redis, // Enables distributed caching across instances
}));API Reference
DevLock Class
| Method | Returns | Description |
|--------|---------|-------------|
| init() | Promise<void> | Initialize SDK, sync config, connect WebSocket |
| validateLicense(key, opts?) | Promise<ValidationResult> | Validate a license key |
| isFeatureEnabled(flag) | boolean | Check feature flag |
| isMaintenanceMode() | boolean | Check maintenance status |
| isKillSwitchActive() | boolean | Check kill switch |
| isApiSuspended() | boolean | Check API suspension |
| getConfig(key, default?) | T | Get remote config value |
| getState() | SDKState | Get full SDK state |
| track(event) | void | Track telemetry event |
| sync() | Promise<void> | Force config re-sync |
| invalidateLicense(key) | Promise<void> | Clear cached validation |
| destroy() | void | Cleanup resources |
ValidationResult
interface ValidationResult {
valid: boolean;
status: 'active' | 'suspended' | 'expired' | 'revoked' | 'trial' | 'unknown';
features: string[];
expiresAt?: string;
error?: string;
cached?: boolean;
}Express Middleware (req.devlock)
interface RequestDevLock {
license: ValidationResult;
isFeatureEnabled: (flag: string) => boolean;
getConfig: <T>(key: string, defaultValue?: T) => T;
track: (event: string, metadata?: Record<string, unknown>) => void;
}License Key Extraction
By default, the middleware looks for the license key in:
X-License-KeyheaderAuthorization: License <key>header?license_key=query parameter
Custom extraction:
app.use(createMiddleware({
...config,
extractLicenseKey: (req) => req.headers['x-my-license'] as string,
}));For single-app deployments (one license for the whole app):
app.use(createMiddleware({
...config,
extractLicenseKey: (_req) => process.env.DEVLOCK_LICENSE_KEY,
}));Fail-open behavior
🔒 The safety guarantee
Your service is disabled ONLY when you explicitly lock it from the DevLock dashboard. A DevLock outage, a network/API error, a request timeout, a malformed response, or the SDK itself failing will never block traffic, return 503/403, or crash your process. Errors always resolve to allow. A block is applied only when the server explicitly reports
enabled: truefor a kill-switch / maintenance / suspension you turned on.
DevLock is a safety layer for your service — it must never become a single point of failure. If DevLock's servers are unreachable and there is no cached decision:
init()never throws (resolves in a permissive offline state).- The middleware does not block traffic — a request that can only be validated as
'unknown'(server unreachable) is allowed through instead of returning403. - Lock flags are normalised — only an explicit
enabled === trueblocks; missing or malformed config can never take your service down.
app.use(createMiddleware({
secretKey: process.env.DEVLOCK_SECRET_KEY!,
projectId: process.env.DEVLOCK_PROJECT_ID!,
failBehavior: 'open', // default — never break the host service
// failBehavior: 'closed', // opt in to blocking when DevLock is unreachable
}));Definitive enforcement signals (kill-switch, API suspension, maintenance, and
suspended / expired / revoked licenses) are cached and still block — once
received they persist across restarts, so an outage cannot be used to bypass a lock
that was already issued.
Local Development & Testing
When integrating the SDK into a local service (e.g., http://localhost:3000), you can test it directly against your production DevLock account.
- No URL changes needed: Do not set
apiUrlorwsUrllocally unless you are running your own self-hosted DevLock server. The SDK will automatically connect to your production DevLock servers. - Domain Lock: For backend API endpoints, domain locking usually applies to where the request originates. If you enforce Domain Locking, ensure
localhostor127.0.0.1is added to the Allowed Domains in your DevLock Dashboard (or leave the list completely empty to allow all domains). - Use
failBehavior: 'open'(default) during development — if DevLock servers are unreachable, your app will still run normally. You'll see[DevLock] Config sync failedin the logs, which is expected in offline/local environments.
Links
License
MIT
Built by Md Tanvir Ahamed Shanto · devlock.tashanto.com
