npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@by-muris/barts-api

v0.1.0

Published

A small Express API framework with OpenAPI generation and dependency injection

Readme

Barts API

@by-muris/barts-api

An opinionated Express API framework for small by-muris services.

@by-muris/barts-api provides:

  • controller and endpoint registration
  • endpoint and controller filters using the ErrorOr result model
  • optional raw Express middleware for third-party integrations
  • OpenAPI document generation from decorated DTO classes
  • an ErrorOr result model with HTTP status mapping
  • a small dependency-injection container

Opinionated By Design

This package deliberately keeps the request flow narrow. Endpoint handlers return ErrorOr<T> values, and the framework converts those results into HTTP responses.

Once you adopt the package, you are intentionally locked into the ErrorOr ecosystem for endpoint and filter results:

return ok({ todos: [] })
return error(ErrorType.Validation, 'title is required')

This is not intended to be an unopinionated collection of Express helpers. The benefit is predictable controller code, consistent HTTP error responses, and a small framework surface.

Installation

Install the package and its peer dependencies:

npm install @by-muris/barts-api express class-validator class-transformer reflect-metadata

If the app exposes Swagger UI, install that separately:

npm install swagger-ui-express
npm install --save-dev @types/swagger-ui-express

Swagger UI stays app-side. The package generates the OpenAPI document but does not decide which URL should expose documentation.

TypeScript Configuration

Decorated DTO classes require decorator metadata:

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

Import reflect-metadata once before registering controllers:

import 'reflect-metadata'

Express Setup

Register controllers before generating the OpenAPI document. Endpoint docs are collected while controllers are registered.

import 'reflect-metadata'
import express from 'express'
import swaggerUi from 'swagger-ui-express'
import { createOpenApiDocument } from '@by-muris/barts-api'
import { todosController } from './controllers/todos.controller.js'

const app = express()

app.use(express.json())

todosController(app)

app.get('/openapi.json', (_req, res) => {
  res.json(createOpenApiDocument())
})

app.use('/docs', swaggerUi.serve, swaggerUi.setup(createOpenApiDocument()))

app.listen(3000)

Controllers And Endpoints

A controller groups endpoints under a shared path:

import { controller, error, ErrorType, ok } from '@by-muris/barts-api'

export const todosController = controller('/todos', ({ endpoint }) => {
  endpoint('/', 'get', () => {
    return ok({
      todos: [],
    })
  })

  endpoint('/', 'post', (req) => {
    const title = req.body?.title

    if (typeof title !== 'string' || !title.trim()) {
      return error(ErrorType.Validation, 'title is required')
    }

    return ok({
      id: crypto.randomUUID(),
      title: title.trim(),
    })
  })
})

Supported endpoint methods:

;'get' | 'post' | 'patch' | 'put' | 'delete'

Successful results default to HTTP 200:

return ok(response)

Select a different success status when needed:

import { ok, ResultType } from '@by-muris/barts-api'

return ok(response, { type: ResultType.Created })

Errors map to consistent HTTP status codes:

import { error, ErrorType } from '@by-muris/barts-api'

return error(ErrorType.NotFound, 'TODO was not found')

Swagger Docs

Use decorated DTO classes for request and response schemas:

import { IsBoolean, IsString, MinLength } from 'class-validator'
import { JSONSchema } from 'class-validator-jsonschema'

export class CreateTodoRequest {
  @IsString()
  @MinLength(1)
  @JSONSchema({ example: 'Buy milk' })
  title!: string
}

export class TodoResponse {
  @IsString()
  @JSONSchema({ example: '8f6f0a2a-48f6-4c15-91a0-3dfb95d72575' })
  id!: string

  @IsString()
  @JSONSchema({ example: 'Buy milk' })
  title!: string

  @IsBoolean()
  @JSONSchema({ example: false })
  completed!: boolean
}

Reference DTO classes from endpoint docs:

import { controller, ok } from '@by-muris/barts-api'
import { CreateTodoRequest, TodoResponse } from './todo.dto.js'

export const todosController = controller('/todos', ({ endpoint }) => {
  endpoint(
    '/',
    'post',
    (req) => {
      return ok({
        id: crypto.randomUUID(),
        title: req.body.title,
        completed: false,
      })
    },
    {
      docs: {
        summary: 'Creates a TODO',
        tags: ['todos'],
        requestBody: CreateTodoRequest,
        responses: {
          200: TodoResponse,
          400: undefined,
          500: undefined,
        },
      },
    },
  )
})

The response value undefined documents a status without a JSON response schema:

responses: {
  204: undefined,
}

Route parameters are detected automatically:

endpoint('/:id', 'patch', handler, {
  docs: {
    summary: 'Updates a TODO',
    responses: {
      200: TodoResponse,
    },
  },
})

This produces an OpenAPI route parameter for {id}.

Dependency Injection

The package includes a deliberately small DI container.

Define a typed token:

import { token } from '@by-muris/barts-api'
import type { Database } from './database.js'

export const DATABASE = token<Database>('DATABASE')

Register a value:

import { register } from '@by-muris/barts-api'
import { DATABASE } from './providers.js'
import { db } from './database.js'

register(DATABASE, db)

Register a singleton factory:

register(DATABASE, () => createDatabase(), {
  lifetime: 'singleton',
})

Register a transient factory:

register(DATABASE, () => createDatabase(), {
  lifetime: 'transient',
})

Inject the value where needed:

import { inject } from '@by-muris/barts-api'
import { DATABASE } from './providers.js'

const db = inject(DATABASE)

The DI container intentionally supports only values and zero-argument factories. It is small enough to understand at a glance.

Auth With Filters

Filters are part of the opinionated ErrorOr request flow. A filter receives the Express request, enriches or inspects it, and returns an ErrorOr result. The framework converts filter errors into HTTP responses before the endpoint handler runs.

First, augment the Express request type:

// src/types/express.d.ts
declare module 'express-serve-static-core' {
  interface Request {
    user?: {
      id: string
      role: 'user' | 'admin'
    }
  }
}

export {}

Create an auth filter factory:

import { error, ErrorType, ok, type FilterFn } from '@by-muris/barts-api'

export function requireAuth(): FilterFn {
  return async (req) => {
    const authorization = req.header('authorization')

    if (!authorization) {
      return error(ErrorType.Unauthorized, 'Unauthorized')
    }

    const user = await verifyToken(authorization)

    if (!user) {
      return error(ErrorType.Unauthorized, 'Unauthorized')
    }

    req.user = user
    return ok(undefined)
  }
}

Apply a filter to one endpoint:

endpoint('/', 'get', handler, {
  filters: [requireAuth()],
})

Apply a filter to every endpoint in a controller:

export const todosController = controller(
  '/todos',
  ({ endpoint }) => {
    endpoint('/', 'get', handler)
    endpoint('/', 'post', createHandler)
  },
  {
    filters: [requireAuth()],
  },
)

Filter factories can take configuration:

import { error, ErrorType, ok, type FilterFn } from '@by-muris/barts-api'

export function requireRole(role: 'user' | 'admin'): FilterFn {
  return (req) => {
    if (req.user?.role !== role) {
      return error(ErrorType.Forbidden, 'Forbidden')
    }

    return ok(undefined)
  }
}

Then compose filters:

filters: [requireAuth(), requireRole('admin')]

Filters run in order:

controller filters -> endpoint filters -> endpoint handler

Raw Express Middleware

Use middlewares when integrating a third-party Express middleware package or when you deliberately need direct access to res and next.

import type { MiddlewareFn } from '@by-muris/barts-api'

const requestLogger: MiddlewareFn = (req, _res, next) => {
  console.log(req.method, req.path)
  next()
}

Apply middleware to one endpoint:

endpoint('/', 'get', handler, {
  middlewares: [requestLogger],
})

Or apply it to every endpoint in a controller:

export const todosController = controller('/todos', registerEndpoints, {
  middlewares: [requestLogger],
})

Middleware runs before filters:

controller middleware -> endpoint middleware -> controller filters -> endpoint filters -> endpoint handler