{"_id":"@by-muris/barts-api","_rev":"2-3a3c4042f3b58d5fb4a8882e15b5e3fe","name":"@by-muris/barts-api","dist-tags":{"latest":"0.3.0"},"versions":{"0.1.0":{"name":"@by-muris/barts-api","version":"0.1.0","_id":"@by-muris/barts-api@0.1.0","maintainers":[{"name":"jirikralovec","email":"jiri.kralovec17@gmail.com"}],"dist":{"shasum":"2dde1590e6a136e5d407e619f384754070eace39","tarball":"https://registry.npmjs.org/@by-muris/barts-api/-/barts-api-0.1.0.tgz","fileCount":42,"integrity":"sha512-IM7nDW2XG7rEsSFr9s9VIAXeazW5kFUXeQ22bAlvN92IlbBig+Dh0qtE8zqmu50aO1vK95gv1hVwGm+FaSy6SA==","signatures":[{"sig":"MEQCIHBZSzAAZGjDWTLuPn/w+i8J42KfHX4JalKawgyZmZlJAiBGYICKGsSxIZ+CBKmq2x+ppw/E6OBWyo/A6eZVxlYMjA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":41174},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"d73fc54c4e72b4703aceb61d587b2a60f5495f11","scripts":{"lint":"eslint .","build":"tsc -p tsconfig.json && tsc-alias -p tsconfig.json --resolve-full-paths --resolve-full-extension .js","format":"prettier . --write","prepack":"npm run build","prepare":"husky","lint:fix":"eslint . --fix","typecheck":"tsc --noEmit","format:check":"prettier . --check","pack:dry-run":"npm pack --dry-run"},"_npmUser":{"name":"jirikralovec","email":"jiri.kralovec17@gmail.com"},"_npmVersion":"11.12.1","description":"A small Express API framework with OpenAPI generation and dependency injection","directories":{},"_nodeVersion":"24.15.0","dependencies":{"class-validator-jsonschema":"^5.1.0"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org"},"_hasShrinkwrap":false,"devDependencies":{"husky":"^9.1.7","eslint":"^9.39.1","express":"^5.1.0","prettier":"^3.6.2","tsc-alias":"^1.8.16","@eslint/js":"^9.39.1","typescript":"^5.9.2","@types/node":"^22.13.0","@types/express":"^5.0.0","class-validator":"^0.14.4","reflect-metadata":"^0.2.2","class-transformer":"^0.5.1","typescript-eslint":"^8.44.1","eslint-config-prettier":"^10.1.8"},"peerDependencies":{"express":"^4.21.0 || ^5.0.0","class-validator":"^0.14.0","reflect-metadata":"^0.1.13 || ^0.2.0","class-transformer":"^0.5.0"},"_npmOperationalInternal":{"tmp":"tmp/barts-api_0.1.0_1780165458845_0.9367583378857085","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"_id":"@by-muris/barts-api@0.3.0","bugs":{"url":"https://github.com/by-muris/barts-api/issues"},"dist":{"shasum":"3b885b8d1ca5ef16e5b8270ebf5256005e1a8d0f","tarball":"https://registry.npmjs.org/@by-muris/barts-api/-/barts-api-0.3.0.tgz","fileCount":60,"integrity":"sha512-H3t71JtDJbKE+OGUUQ5L2Y4b4FV5OntR0EO2fqwRa8K2MRAkH5gvavp/B6+j+4wsc0L6xbgYKKju1ySBjfjoIA==","signatures":[{"sig":"MEYCIQDuMAkmcAPRoAfQviwnwN+B2NxUuLrtf2RSSBJ/4t0V8AIhAPQ3e3pONp0gPERUW4wsDf3WIbXk+nMNPZIGitaFEJTa","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDthxiBUCCMtxhJHNNeC+rrBFQFIt3FcaFn4WZoXmMiWwIgUNiSb4gnGgOq43mTkJmpgnwQhKC5FbNjN/+MR/QnezU="}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@by-muris%2fbarts-api@0.3.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":49544},"main":"./dist/index.js","name":"@by-muris/barts-api","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./logging":{"types":"./dist/packages/logging/index.d.ts","import":"./dist/packages/logging/index.js"}},"gitHead":"2b7b1c4abcbbfe9b4e72e60cbb63c27e6b211fc9","scripts":{"lint":"eslint .","build":"tsc -p tsconfig.json && tsc-alias -p tsconfig.json --resolve-full-paths --resolve-full-extension .js","format":"prettier . --write","prepack":"npm run build","prepare":"husky","lint:fix":"eslint . --fix","typecheck":"tsc --noEmit","format:check":"prettier . --check","pack:dry-run":"npm pack --dry-run"},"version":"0.3.0","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:7fa61a52-01c9-4dd8-bf1f-1f557bdbb99d"}},"homepage":"https://github.com/by-muris/barts-api#readme","repository":{"url":"git+https://github.com/by-muris/barts-api.git","type":"git"},"_npmVersion":"11.12.1","description":"A small Express API framework with OpenAPI generation and dependency injection","directories":{},"maintainers":[{"name":"jirikralovec","email":"jiri.kralovec17@gmail.com"}],"_nodeVersion":"24.15.0","publishConfig":{"access":"public","registry":"https://registry.npmjs.org"},"_hasShrinkwrap":false,"devDependencies":{"husky":"^9.1.7","eslint":"^9.39.1","express":"^5.1.0","prettier":"^3.6.2","tsc-alias":"^1.8.16","@eslint/js":"^9.39.1","typescript":"^5.9.2","@types/node":"^22.13.0","@types/express":"^5.0.0","class-validator":"^0.14.4","reflect-metadata":"^0.2.2","class-transformer":"^0.5.1","typescript-eslint":"^8.44.1","eslint-config-prettier":"^10.1.8","class-validator-jsonschema":"^5.1.0"},"peerDependencies":{"express":"^4.21.0 || ^5.0.0","class-validator":"^0.14.0","reflect-metadata":"^0.1.13 || ^0.2.0","class-transformer":"^0.5.0","class-validator-jsonschema":"^5.1.0"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/barts-api_0.3.0_1789591003083_0.41373092490429575"}}},"time":{"created":"2026-05-30T18:24:18.722Z","modified":"2026-09-16T20:36:43.454Z","0.1.0":"2026-05-30T18:24:18.973Z","0.3.0":"2026-09-16T20:36:43.168Z"},"description":"A small Express API framework with OpenAPI generation and dependency injection","maintainers":[{"name":"jirikralovec","email":"jiri.kralovec17@gmail.com"}],"readme":"![Barts API](.github/cat.png)\n\n# @by-muris/barts-api\n\nAn opinionated Express API framework for small `by-muris` services.\n\n`@by-muris/barts-api` provides:\n\n- controller and endpoint registration\n- endpoint and controller filters using the `ErrorOr` result model\n- optional raw Express middleware for third-party integrations\n- OpenAPI document generation from decorated DTO classes\n- an `ErrorOr` result model with HTTP status mapping\n- a small dependency-injection container\n\n## Opinionated By Design\n\nThis package deliberately keeps the request flow narrow. Endpoint handlers return\n`ErrorOr<T>` values, and the framework converts those results into HTTP responses.\n\nOnce you adopt the package, you are intentionally locked into the `ErrorOr` ecosystem\nfor endpoint and filter results:\n\n```ts\nreturn ok({ todos: [] })\nreturn error(ErrorType.Validation, 'title is required')\n```\n\nThis is not intended to be an unopinionated collection of Express helpers. The benefit\nis predictable controller code, consistent HTTP error responses, and a small framework\nsurface.\n\n## Installation\n\nInstall the package and its peer dependencies:\n\n```bash\nnpm install @by-muris/barts-api express class-validator class-transformer class-validator-jsonschema reflect-metadata\n```\n\nIf the app exposes Swagger UI, install that separately:\n\n```bash\nnpm install swagger-ui-express\nnpm install --save-dev @types/swagger-ui-express\n```\n\nSwagger UI stays app-side. The package generates the OpenAPI document but does not\ndecide which URL should expose documentation.\n\n## Runnable Example\n\n[`example/`](example) is a small local consumer project. It imports this repository\nthrough a `file:..` dependency and demonstrates controllers, DTO validation, OpenAPI\nJSON, and Swagger UI.\n\n```bash\ncd example\nnpm install\nnpm run dev\n```\n\nOpen `http://localhost:3000/docs/` for Swagger UI or\n`http://localhost:3000/openapi.json` for the generated OpenAPI document. WebStorm-ready\nrequests are in [`example/http/`](example/http).\n\n## TypeScript Configuration\n\nDecorated DTO classes require decorator metadata:\n\n```json\n{\n  \"compilerOptions\": {\n    \"experimentalDecorators\": true,\n    \"emitDecoratorMetadata\": true\n  }\n}\n```\n\nImport `reflect-metadata` once before registering controllers:\n\n```ts\nimport 'reflect-metadata'\n```\n\n## Express Setup\n\nRegister controllers before generating the OpenAPI document. Endpoint docs are collected\nwhile controllers are registered.\n\n```ts\nimport 'reflect-metadata'\nimport express from 'express'\nimport swaggerUi from 'swagger-ui-express'\nimport { createOpenApiDocument } from '@by-muris/barts-api'\nimport { todosController } from './controllers/todos.controller.js'\n\nconst app = express()\n\napp.use(express.json())\n\ntodosController(app)\n\napp.get('/openapi.json', (_req, res) => {\n  res.json(createOpenApiDocument())\n})\n\napp.use('/docs', swaggerUi.serve, swaggerUi.setup(createOpenApiDocument()))\n\napp.listen(3000)\n```\n\n## Controllers And Endpoints\n\nA controller groups endpoints under a shared path:\n\n```ts\nimport { controller, error, ErrorType, ok } from '@by-muris/barts-api'\n\nexport const todosController = controller('/todos', ({ endpoint }) => {\n  endpoint('/', 'get', () => {\n    return ok({\n      todos: [],\n    })\n  })\n\n  endpoint('/', 'post', (req) => {\n    const title = req.body?.title\n\n    if (typeof title !== 'string' || !title.trim()) {\n      return error(ErrorType.Validation, 'title is required')\n    }\n\n    return ok({\n      id: crypto.randomUUID(),\n      title: title.trim(),\n    })\n  })\n})\n```\n\nSupported endpoint methods:\n\n```ts\n;'get' | 'post' | 'patch' | 'put' | 'delete'\n```\n\nSuccessful results default to HTTP `200`:\n\n```ts\nreturn ok(response)\n```\n\nSelect a different success status when needed:\n\n```ts\nimport { ok, ResultType } from '@by-muris/barts-api'\n\nreturn ok(response, { type: ResultType.Created })\n```\n\nErrors map to consistent HTTP status codes:\n\n```ts\nimport { error, ErrorType } from '@by-muris/barts-api'\n\nreturn error(ErrorType.NotFound, 'TODO was not found')\n```\n\n## Swagger Docs\n\nUse decorated DTO classes for request and response schemas:\n\n```ts\nimport { IsBoolean, IsString, MinLength } from 'class-validator'\nimport { JSONSchema } from 'class-validator-jsonschema'\n\nexport class CreateTodoRequest {\n  @IsString()\n  @MinLength(1)\n  @JSONSchema({ example: 'Buy milk' })\n  title!: string\n}\n\nexport class TodoResponse {\n  @IsString()\n  @JSONSchema({ example: '8f6f0a2a-48f6-4c15-91a0-3dfb95d72575' })\n  id!: string\n\n  @IsString()\n  @JSONSchema({ example: 'Buy milk' })\n  title!: string\n\n  @IsBoolean()\n  @JSONSchema({ example: false })\n  completed!: boolean\n}\n```\n\nReference DTO classes from endpoint docs:\n\n```ts\nimport { controller, ok } from '@by-muris/barts-api'\nimport { CreateTodoRequest, TodoResponse } from './todo.dto.js'\n\nexport const todosController = controller('/todos', ({ endpoint }) => {\n  endpoint(\n    '/',\n    'post',\n    (req) => {\n      return ok({\n        id: crypto.randomUUID(),\n        title: req.body.title,\n        completed: false,\n      })\n    },\n    {\n      docs: {\n        summary: 'Creates a TODO',\n        tags: ['todos'],\n        requestBody: CreateTodoRequest,\n        responses: {\n          200: TodoResponse,\n          400: undefined,\n          500: undefined,\n        },\n      },\n    },\n  )\n})\n```\n\nThe response value `undefined` documents a status without a JSON response schema:\n\n```ts\nresponses: {\n  204: undefined,\n}\n```\n\nRoute parameters are detected automatically:\n\n```ts\nendpoint('/:id', 'patch', handler, {\n  docs: {\n    summary: 'Updates a TODO',\n    responses: {\n      200: TodoResponse,\n    },\n  },\n})\n```\n\nThis produces an OpenAPI route parameter for `{id}`.\n\n## Dependency Injection\n\nThe package includes a deliberately small DI container.\n\nDefine a typed token:\n\n```ts\nimport { token } from '@by-muris/barts-api'\nimport type { Database } from './database.js'\n\nexport const DATABASE = token<Database>('DATABASE')\n```\n\nRegister a value:\n\n```ts\nimport { register } from '@by-muris/barts-api'\nimport { DATABASE } from './providers.js'\nimport { db } from './database.js'\n\nregister(DATABASE, db)\n```\n\nRegister a singleton factory:\n\n```ts\nregister(DATABASE, () => createDatabase(), {\n  lifetime: 'singleton',\n})\n```\n\nRegister a transient factory:\n\n```ts\nregister(DATABASE, () => createDatabase(), {\n  lifetime: 'transient',\n})\n```\n\nInject the value where needed:\n\n```ts\nimport { inject } from '@by-muris/barts-api'\nimport { DATABASE } from './providers.js'\n\nconst db = inject(DATABASE)\n```\n\nThe DI container intentionally supports only values and zero-argument factories. It is\nsmall enough to understand at a glance.\n\n## Auth With Filters\n\nFilters are part of the opinionated `ErrorOr` request flow. A filter receives the\nExpress request, enriches or inspects it, and returns an `ErrorOr` result. The framework\nconverts filter errors into HTTP responses before the endpoint handler runs.\n\nFirst, augment the Express request type:\n\n```ts\n// src/types/express.d.ts\ndeclare module 'express-serve-static-core' {\n  interface Request {\n    user?: {\n      id: string\n      role: 'user' | 'admin'\n    }\n  }\n}\n\nexport {}\n```\n\nCreate an auth filter factory:\n\n```ts\nimport { error, ErrorType, ok, type FilterFn } from '@by-muris/barts-api'\n\nexport function requireAuth(): FilterFn {\n  return async (req) => {\n    const authorization = req.header('authorization')\n\n    if (!authorization) {\n      return error(ErrorType.Unauthorized, 'Unauthorized')\n    }\n\n    const user = await verifyToken(authorization)\n\n    if (!user) {\n      return error(ErrorType.Unauthorized, 'Unauthorized')\n    }\n\n    req.user = user\n    return ok(undefined)\n  }\n}\n```\n\nApply a filter to one endpoint:\n\n```ts\nendpoint('/', 'get', handler, {\n  filters: [requireAuth()],\n})\n```\n\nApply a filter to every endpoint in a controller:\n\n```ts\nexport const todosController = controller(\n  '/todos',\n  ({ endpoint }) => {\n    endpoint('/', 'get', handler)\n    endpoint('/', 'post', createHandler)\n  },\n  {\n    filters: [requireAuth()],\n  },\n)\n```\n\nFilter factories can take configuration:\n\n```ts\nimport { error, ErrorType, ok, type FilterFn } from '@by-muris/barts-api'\n\nexport function requireRole(role: 'user' | 'admin'): FilterFn {\n  return (req) => {\n    if (req.user?.role !== role) {\n      return error(ErrorType.Forbidden, 'Forbidden')\n    }\n\n    return ok(undefined)\n  }\n}\n```\n\nThen compose filters:\n\n```ts\nfilters: [requireAuth(), requireRole('admin')]\n```\n\nFilters run in order:\n\n```text\ncontroller filters -> endpoint filters -> endpoint handler\n```\n\n## Raw Express Middleware\n\nUse `middlewares` when integrating a third-party Express middleware package or when\nyou deliberately need direct access to `res` and `next`.\n\n```ts\nimport type { MiddlewareFn } from '@by-muris/barts-api'\n\nconst requestLogger: MiddlewareFn = (req, _res, next) => {\n  console.log(req.method, req.path)\n  next()\n}\n```\n\nApply middleware to one endpoint:\n\n```ts\nendpoint('/', 'get', handler, {\n  middlewares: [requestLogger],\n})\n```\n\nOr apply it to every endpoint in a controller:\n\n```ts\nexport const todosController = controller('/todos', registerEndpoints, {\n  middlewares: [requestLogger],\n})\n```\n\nMiddleware runs before filters:\n\n```text\ncontroller middleware -> endpoint middleware -> controller filters -> endpoint filters -> endpoint handler\n```\n","readmeFilename":"README.md","homepage":"https://github.com/by-muris/barts-api#readme","repository":{"url":"git+https://github.com/by-muris/barts-api.git","type":"git"},"bugs":{"url":"https://github.com/by-muris/barts-api/issues"}}