Skip to content

05Project

Node.js JavaScript example

A plain JavaScript Node.js script using a client generated as JavaScript with JSDoc and .d.ts declarations from an OpenAPI 3.0 document (openapi.yaml).

Terminal window
pnpm example:node-javascript # from the repository root: build swaggerz, generate, run

Or, inside this directory once swaggerz is built:

Terminal window
pnpm generate # src/generated/api (.js + .d.ts)
pnpm start # starts a local in-memory API (src/server.js) and calls it
pnpm check # type-checks the JavaScript with `checkJs`

src/index.js configures the base URL, the apiKey security scheme and a logging middleware, then creates and lists tasks (with an exploded array query parameter) and handles a 404 with the operation’s error guard, which types the body by status. Editors show the generated types and documentation for every call.

Expected output (ports vary):

createTask: POST http://127.0.0.1:53816/tasks → 201
Created task 2: Try swaggerz (todo)
listTasks: GET http://127.0.0.1:53816/tasks?tag=demo&tag=docs → 200
Tasks tagged demo and docs: Try swaggerz
getTask: GET http://127.0.0.1:53816/tasks/404 → 404
Expected failure: HTTP 404 — No task at /tasks/404

Config

swaggerz.config.mjs
// A JavaScript project: the generated client is JavaScript with JSDoc and `.d.ts` declarations.
import { defineConfig } from 'swaggerz';
export default defineConfig({
spec: { version: '3.0', input: './openapi.yaml' },
output: { directory: './src/generated/api', language: 'javascript' },
});

Sources

src/index.js
// @ts-check
import {
ApiError,
RequestError,
client,
createTask,
getTask,
isGetTaskApiError,
listTasks,
} from './generated/api/index.js';
import { startServer } from './server.js';
const apiKey = process.env.TASKS_API_KEY ?? 'demo-key';
const server = await startServer({ apiKey });
client.setConfig({
baseUrl: server.url,
timeout: 5_000,
// Values for the security schemes in openapi.yaml. Functions are called for every request.
auth: { apiKey: () => apiKey },
});
// Middleware sees every request and response.
client.use({
onResponse({ request, response, operation }) {
console.log(
` ${operation.operationId}: ${request.method} ${request.url} → ${response.status}`,
);
},
});
try {
// Parameters and the request body are checked by the editor through the generated .d.ts files.
const task = await createTask({
body: { title: 'Try swaggerz', tags: ['demo', 'docs'], dueDate: '2026-12-31' },
});
console.log(`Created task ${task.id}: ${task.title} (${task.status})`);
// `tag` is an exploded array query parameter: ?tag=demo&tag=docs
const tagged = await listTasks({ tag: ['demo', 'docs'] });
console.log(`Tasks tagged demo and docs: ${tagged.map((t) => t.title).join(', ')}`);
await getTask({ taskId: 404 });
} catch (error) {
if (isGetTaskApiError(error) && error.status === 404) {
// The guard types errors by status: a 404 from getTask has a Problem body.
console.log(`Expected failure: HTTP ${error.status} — ${error.body.title}`);
} else if (error instanceof ApiError) {
console.error(`Unexpected HTTP ${error.status} from ${error.operationId}`);
process.exitCode = 1;
} else if (error instanceof RequestError) {
console.error(`Request failed (${error.kind}): ${error.message}`);
process.exitCode = 1;
} else {
throw error;
}
} finally {
server.close();
}
src/server.js
// @ts-check
/**
* A tiny in-memory implementation of openapi.yaml so the example runs without a backend.
* Replace it with your real API by changing `baseUrl`.
*/
import http from 'node:http';
/** @typedef {import('./generated/api/index.js').Task} Task */
/** @typedef {import('./generated/api/index.js').NewTask} NewTask */
/** @type {Task[]} */
const tasks = [{ id: 1, title: 'Read the README', status: 'done', tags: ['docs'], dueDate: null }];
/**
* Starts the server on a random local port.
*
* @param {{ apiKey: string }} options
* @returns {Promise<{ url: string, close: () => void }>}
*/
export async function startServer({ apiKey }) {
const server = http.createServer((req, res) => {
/** @param {number} status @param {unknown} body */
const send = (status, body) => {
res.writeHead(status, { 'content-type': 'application/json' });
res.end(JSON.stringify(body));
};
if (req.headers['x-api-key'] !== apiKey)
return send(401, { title: 'Missing or wrong API key' });
const url = new URL(req.url ?? '/', 'http://localhost');
if (url.pathname === '/tasks' && req.method === 'GET') {
const status = url.searchParams.get('status');
const tags = url.searchParams.getAll('tag');
return send(
200,
tasks.filter(
(task) =>
(status === null || task.status === status) &&
tags.every((tag) => task.tags.includes(tag)),
),
);
}
if (url.pathname === '/tasks' && req.method === 'POST') {
let raw = '';
req.setEncoding('utf8');
req.on('data', (chunk) => (raw += chunk));
req.on('end', () => {
/** @type {NewTask} */
const input = JSON.parse(raw);
/** @type {Task} */
const task = {
id: tasks.length + 1,
title: input.title,
status: 'todo',
tags: input.tags ?? [],
dueDate: input.dueDate ?? null,
};
tasks.push(task);
send(201, task);
});
return;
}
const match = /^\/tasks\/(\d+)$/.exec(url.pathname);
const task = match && tasks.find((t) => t.id === Number(match[1]));
if (task) return send(200, task);
send(404, { title: `No task at ${url.pathname}` });
});
await new Promise((resolve) => server.listen(0, '127.0.0.1', () => resolve(undefined)));
const address = server.address();
const port = typeof address === 'object' && address !== null ? address.port : 0;
return { url: `http://127.0.0.1:${port}`, close: () => server.close() };
}

Generated files

32 files for 3 operations and 4 models, generated with this config when the site was built:

  • client.d.ts, client.js, index.d.ts, index.js
  • models/8 files
  • runtime/14 files
  • tasks/6 files

The project on GitHub