@archie/node-sdk
v0.1.1
Published
Archie BAAS - Node.js server SDK with admin auth, JWT verification & middleware
Downloads
251
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.
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.
