@archie/node-sdk
v0.4.1
Published
Archie BAAS - Node.js server SDK with admin auth, JWT verification & middleware
Readme
@archie/node-sdk
Node.js server SDK for the Archie BAAS platform. JWT verification, admin user management, service-role GraphQL & REST, and framework middleware for Express, Fastify, and Lambda.
Install
# npm
npm install @archie/js-sdk @archie/node-sdk
# pnpm
pnpm add @archie/js-sdk @archie/node-sdk@archie/js-sdk is a required peer dependency. Framework integrations (express, fastify) are optional — install only what you use.
Requirements: Node.js >= 18.0.0
Quick Start
import { createAdminClient } from '@archie/node-sdk';
const archie = createAdminClient({
projectId: 'your-project-uuid',
apiKey: 'your-service-role-api-key',
apiUrl: 'https://your-project.archiecore.com',
environment: 'master',
retries: 3,
timeout: 15_000,
});
// Health check
const healthy = await archie.ping();
// Verify a JWT
const { user, error } = await archie.auth.verifyToken(token);
// List users
const { data, error: listErr } = await archie.auth.listUsers({ page: 1, limit: 20 });
// Service-role GraphQL
const { data: roles } = await archie.graphql.query('{ roles { items { id name } } }');API Reference
createAdminClient(options)
Creates an admin client for server-side operations using a service-role API key.
const archie = createAdminClient({
projectId: 'uuid',
apiKey: 'service-role-api-key',
environment: 'master',
timeout: 15_000,
retries: 3,
});| Option | Type | Default | Description |
| ------------- | -------------- | -------------------------- | ---------------------------------------------------------------- |
| projectId | string | — | Required. Project UUID. |
| apiKey | string | — | Required. Service-role API key. |
| environment | string | 'master' | Environment name. |
| apiUrl | string | 'https://api.archie.dev' | Base URL for the API. |
| authUrl | string | same as apiUrl | Base URL for the Auth service. |
| timeout | number | 30000 | Request timeout in milliseconds. Set 0 to disable. |
| retries | number | 0 | Max retry attempts for transient errors (429, 500–504, network). |
| retryDelay | number | 200 | Initial delay in ms for exponential backoff with jitter. |
| fetch | typeof fetch | globalThis.fetch | Custom fetch implementation for testing or proxies. |
| logger | ArchieLogger | {} | Logger for request lifecycle events. |
Returns an ArchieAdminClient with three modules:
archie.auth— JWT verification & admin user operationsarchie.graphql— Service-role GraphQL queries & mutationsarchie.rest— Service-role REST requests
JWT Verification
Verify JWTs issued by Archie using JWKS (JSON Web Key Set). Keys are cached for 5 minutes with automatic deduplication of concurrent fetches.
archie.auth.verifyToken(token, options?)
const { user, error } = await archie.auth.verifyToken('Bearer eyJhbG...');Parameters:
| Parameter | Type | Description |
| ------------------------------ | ---------- | ------------------------------------------------------------ |
| token | string | JWT string. Bearer prefix is stripped automatically. |
| options.requiredRoles | string[] | Require at least one of these roles. Returns 403 if missing. |
| options.requireEmailVerified | boolean | Require emailVerified: true. Returns 403 if false. |
| options.clockTolerance | number | Clock tolerance in seconds. Default: 30. |
Returns: Promise<VerifyTokenResult>
interface VerifyTokenResult {
user: TokenUser | null;
error: ArchieError | null;
}
interface TokenUser {
id: string;
email: string;
firstName?: string;
lastName?: string;
roles: string[];
exp: number;
iat: number;
emailVerified: boolean;
}Examples:
// Basic verification
const { user, error } = await archie.auth.verifyToken(jwt);
if (error) {
console.error(error.code); // AUTH_TOKEN_INVALID, AUTH_TOKEN_EXPIRED, etc.
}
// Require admin role
const { user, error } = await archie.auth.verifyToken(jwt, {
requiredRoles: ['admin'],
});
// Require verified email
const { user, error } = await archie.auth.verifyToken(jwt, {
requireEmailVerified: true,
});JWKS Caching: The SDK automatically fetches and caches JWKS keys for 5 minutes. Concurrent verifyToken calls share a single JWKS fetch, preventing thundering-herd issues.
Admin User Operations
All admin operations use service-role authentication and return { data, error }.
archie.auth.createUser(params)
const { data, error } = await archie.auth.createUser({
email: '[email protected]',
password: 'SecurePass123!',
firstName: 'John', // optional
lastName: 'Doe', // optional
roleId: 'role-uuid', // optional
emailVerified: true, // optional
});archie.auth.listUsers(params?)
const { data, error } = await archie.auth.listUsers({
page: 1, // optional
limit: 20, // optional
search: 'john', // optional, searches email/name
status: 'active', // optional: 'active' | 'locked' | 'unverified'
orderBy: 'createdAt', // optional
});
// data: { users: User[], total: number, page: number, limit: number }archie.auth.getUser(userId)
const { data, error } = await archie.auth.getUser('user-uuid');
// data: User { id, email, firstName, lastName, roles }archie.auth.updateUser(userId, updates)
Accepts Partial<User> — any combination of firstName, lastName, email, roles, etc.:
const { data, error } = await archie.auth.updateUser('user-uuid', {
firstName: 'Jane',
lastName: 'Smith',
});archie.auth.blockUser(userId)
const { data, error } = await archie.auth.blockUser('user-uuid');
// data: booleanarchie.auth.unblockUser(userId)
const { data, error } = await archie.auth.unblockUser('user-uuid');
// data: booleanarchie.auth.deleteUser(userId)
const { data, error } = await archie.auth.deleteUser('user-uuid');
// data: booleanarchie.auth.resendVerification(userId)
const { data, error } = await archie.auth.resendVerification('user-uuid');
// data: booleanarchie.auth.resetUserPassword(userId, newPassword)
const { data, error } = await archie.auth.resetUserPassword('user-uuid', 'NewPass123!');
// data: booleanService-Role GraphQL
Execute GraphQL operations with admin-level permissions.
archie.graphql.query<T>(gql, variables?, options?)
const { data, error } = await archie.graphql.query<{ users: User[] }>(
'{ users { id email roles } }',
);archie.graphql.mutate<T>(gql, variables?, options?)
const { data, error } = await archie.graphql.mutate(
`mutation UpdateRole($userId: ID!, $roleId: ID!) {
assignRole(userId: $userId, roleId: $roleId) { id roles }
}`,
{ userId: 'user-uuid', roleId: 'admin-role' },
);User Impersonation
Execute queries as a specific user while retaining service-role access:
const { data } = await archie.graphql.query(
'{ myOrders { id total } }',
{},
{ impersonateUserId: 'user-uuid' },
);This adds the x-impersonate-user header to the request.
Service-Role REST
Direct REST API access with admin credentials.
const items = await archie.rest.get<Item[]>('/api/v1/items');
const created = await archie.rest.post<Item>('/api/v1/items', { name: 'New' });
const updated = await archie.rest.put<Item>('/api/v1/items/1', { name: 'Updated' });
const patched = await archie.rest.patch<Item>('/api/v1/items/1', { status: 'active' });
await archie.rest.delete('/api/v1/items/1');All methods throw ArchieError on non-2xx responses with the HTTP status code embedded:
try {
await archie.rest.get('/api/v1/missing');
} catch (err) {
// err.code === 'HTTP_404'
// err.status === 404
}Note: Endpoints returning 204 No Content or Content-Length: 0 resolve with null data instead of attempting to parse an empty body.
Emit notifications (server)
Dispatches a notification via the backend emitNotification mutation. eventKey,
target, and sourceService are required.
const { data, error } = await archie.notifications.emit({
eventKey: 'welcome',
target: { userIds: ['<uuid>'] }, // or { audience: 'ALL_PROJECT_USERS' }
sourceService: 'auth', // required — identifies the emitting service
params: { name: 'Ana' },
channels: ['email', 'in_app'], // 'email' | 'in_app' | 'slack' | 'sms' | 'whatsapp'
// optional: action: { text, url }, dedupKey, expiresIn: '7d', broadcastConfirm
});
if (data?.accepted) {
console.log(data.targetingMode, data.recipientCount, data.fanoutJobId);
}Error-contract note. Admin emit() follows the node-sdk convention and always returns { data, error } — it never throws. This is intentional and differs from the browser-side (@archie/js-sdk) notification methods (list, getUnreadCount, markAsRead, markAllAsRead), which throw ArchieError on failure. The asymmetry reflects the boundary between server code (where { data, error } pairs are idiomatic for structured error handling) and client code (where thrown errors integrate naturally with try/catch and React error boundaries).
Express Middleware
npm install expresscreateArchieMiddleware(client, options?)
Verifies JWT from the Authorization header and populates req.archieUser.
import express from 'express';
import { createAdminClient } from '@archie/node-sdk';
import { createArchieMiddleware, requireRole } from '@archie/node-sdk/express';
const archie = createAdminClient({ projectId: '...', apiKey: '...' });
const app = express();
// Optional auth — req.archieUser may be undefined
app.use(createArchieMiddleware(archie, { required: false }));
app.get('/api/profile', (req, res) => {
if (!req.archieUser) return res.status(401).json({ error: 'Unauthorized' });
res.json({ user: req.archieUser });
});
// Required auth — returns 401 automatically
app.use('/api/admin', createArchieMiddleware(archie, { required: true }));
app.get('/api/admin/dashboard', requireRole('admin'), (req, res) => {
res.json({ admin: true, user: req.archieUser });
});| Option | Type | Default | Description |
| ---------- | ---------- | ------- | ------------------------------------------------------ |
| required | boolean | false | Return 401 for unauthenticated requests. |
| onError | function | — | Custom error handler: (err, req, res, next) => void. |
requireRole(...roles)
Express middleware that checks if req.archieUser has at least one of the specified roles. Returns 403 if not. Must be used after createArchieMiddleware.
app.get('/admin', requireRole('admin'), handler);
app.get('/editor', requireRole('admin', 'editor'), handler);TypeScript
The middleware augments Express types — req.archieUser is typed as TokenUser | undefined:
app.get('/api/me', (req, res) => {
const user = req.archieUser; // TokenUser | undefined
res.json({ user });
});Fastify Plugin
npm install fastifyarchiePlugin
Fastify plugin that verifies JWT and decorates request.archieUser.
import Fastify from 'fastify';
import { createAdminClient } from '@archie/node-sdk';
import { archiePlugin } from '@archie/node-sdk/fastify';
const archie = createAdminClient({ projectId: '...', apiKey: '...' });
const fastify = Fastify();
fastify.register(archiePlugin, {
client: archie,
required: false, // set true to enforce auth on all routes
});
fastify.get('/api/profile', async (request) => {
return { user: request.archieUser };
});Lambda / Serverless
withArchieAuth(client, handler)
Wraps a Lambda handler to verify JWT and inject user into context.
import { createAdminClient } from '@archie/node-sdk';
import { withArchieAuth } from '@archie/node-sdk/lambda';
const archie = createAdminClient({ projectId: '...', apiKey: '...' });
export const handler = withArchieAuth(archie, async (event, context) => {
const { user, client } = context.archie;
// Access the verified user
console.log('User:', user.id, user.email);
// Use admin operations
const { data } = await client.auth.listUsers();
return {
statusCode: 200,
body: JSON.stringify({ userId: user.id, totalUsers: data?.total }),
};
});Returns 401 if no Authorization header is present or if the token is invalid.
Error Handling
All operations follow consistent error patterns:
- Auth operations return
{ data, error }whereerrorisArchieError | null - Verify operations return
{ user, error }whereerrorisAuthError | null - REST operations throw
ArchieErroron non-2xx responses - GraphQL operations return
{ data, error }with structured GraphQL errors
Error Codes
| Code | Status | Description |
| ------------------------- | ------ | ------------------------------------------------------ |
| AUTH_TOKEN_INVALID | 401 | Token is missing, malformed, or has invalid signature. |
| AUTH_TOKEN_EXPIRED | 401 | Token has expired. |
| AUTH_INSUFFICIENT_ROLE | 403 | User lacks a required role. |
| AUTH_EMAIL_NOT_VERIFIED | 403 | Email is not verified but was required. |
| AUTH_JWKS_FETCH_FAILED | varies | Failed to fetch JWKS keys. |
| ADMIN_ERROR | varies | Admin operation returned an error. |
| HTTP_<status> | varies | REST request failed with given HTTP status. |
| TIMEOUT | 408 | Request timed out. |
| NETWORK_ERROR | 0 | Network-level failure. |
Retry & Timeout
All HTTP requests go through a centralized engine with configurable retry and timeout.
Retry
Enable automatic retries for transient failures:
const archie = createAdminClient({
projectId: '...',
apiKey: '...',
retries: 3, // retry up to 3 times
retryDelay: 200, // start with 200ms delay
});Retryable conditions:
- HTTP status codes:
429,500,502,503,504 - Network errors (DNS failures, connection refused, etc.)
- Timeout errors
Backoff strategy: Exponential with jitter — delay × 2^attempt + random jitter, capped at 30 seconds. When a 429 response includes a Retry-After header, the SDK respects it.
Timeout
All requests have a 30-second timeout by default:
const archie = createAdminClient({
projectId: '...',
apiKey: '...',
timeout: 10_000, // 10s timeout
});Set timeout: 0 to disable timeouts entirely. When a request times out, an ArchieError is thrown with code: 'TIMEOUT' and status: 408.
Custom Fetch
Inject a custom fetch implementation for testing, proxies, or request interceptors:
import { createAdminClient } from '@archie/node-sdk';
const archie = createAdminClient({
projectId: '...',
apiKey: '...',
fetch: myCustomFetch,
});Use cases:
- Unit testing with mocked fetch
- HTTP proxy / agent injection (e.g.,
undiciwith proxy) - Request/response logging at the transport level
Logger
Plug in any logger (pino, winston, console, custom) to observe request lifecycle events:
import pino from 'pino';
import { createAdminClient } from '@archie/node-sdk';
const logger = pino({ level: 'debug' });
const archie = createAdminClient({
projectId: '...',
apiKey: '...',
logger: {
debug: (msg, meta) => logger.debug(meta, msg),
warn: (msg, meta) => logger.warn(meta, msg),
},
});ArchieLogger interface
interface ArchieLogger {
debug?: (message: string, meta?: Record<string, unknown>) => void;
info?: (message: string, meta?: Record<string, unknown>) => void;
warn?: (message: string, meta?: Record<string, unknown>) => void;
error?: (message: string, meta?: Record<string, unknown>) => void;
}Events logged:
| Level | Event | Meta |
|-------|-------|------|
| debug | Request | { method, url } |
| debug | Response | { url, status } |
| warn | Retrying request | { url, attempt, delay } |
| warn | Rate limited, waiting Retry-After | { url, retryAfter } |
| warn | Network error, will retry | { url, attempt, error } |
All logger methods are optional — use only the levels you need.
Health Check
Verify API connectivity:
const healthy = await archie.ping();
if (!healthy) {
console.error('Archie API is unreachable');
}ping() sends a GET /health request and returns true if the server responds with 2xx within the configured timeout. Returns false on any error.
TypeScript
Exported Types
import type {
AdminClientOptions,
ArchieLogger,
VerifyTokenOptions,
VerifyTokenResult,
TokenUser,
CreateUserParams,
ListUsersParams,
ListUsersResult,
MiddlewareOptions,
} from '@archie/node-sdk';
import type {
IAdminAuthModule,
IAdminGraphQLModule,
IAdminRestModule,
AdminResult,
} from '@archie/node-sdk';Classes
The ArchieAdminClient class and concrete module classes are exported for instanceof checks, advanced typing, or mocking:
import {
ArchieAdminClient,
AdminAuthModule,
AdminGraphQLModule,
AdminRestModule,
} from '@archie/node-sdk';
// Direct instantiation (equivalent to createAdminClient)
const archie = new ArchieAdminClient({ projectId: '...', apiKey: '...' });Re-exported from @archie/js-sdk
import type {
User,
Session,
GraphQLResponse,
ArchieError,
AuthError,
GraphQLError,
NetworkError,
} from '@archie/node-sdk';Playground
An interactive playground is available in examples/playground-node/:
cd examples/playground-node
cp .env.example .env
# Edit .env with your project credentials
pnpm install
pnpm dev
# Open http://localhost:3456The playground provides a web dashboard to test all node-sdk features: JWT verification, admin user operations, service-role GraphQL, REST, and Express middleware.
