Skip to content

w3cj/stoker

Repository files navigation

stoker

npm version npm downloads bundle JSDocs License

stoke the flame 🤙🔥

Utilities for hono and @hono/zod-openapi.

To see real world usage of these utilities, checkout the hono-open-api-starter routes example

Utilities

stoker/http-status-codes

HTTP status code constants. Provides individual typed / documented exports. Use anywhere you need a status code instead of hard coding raw numbers.

Sourced from http-status-codes | RFC1945 (HTTP/1.0), RFC2616 (HTTP/1.1), RFC2518 (WebDAV), RFC6585 (Additional HTTP Status Codes), and RFC7538 (Permanent Redirect).

Why not use the http-status-codes package directly? http-status-codes exports enums which do not work well with the @hono/zod-openapi type system and the built in StatusCode type from hono/utils/http-status.

Example Usage

import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi";
import * as HttpStatusCodes from "stoker/http-status-codes";

const app = new OpenAPIHono();

app.notFound((c) => {
  return c.json({
    message: `Not Found - ${c.req.path}`,
  }, HttpStatusCodes.NOT_FOUND);
});

app.onError((err, c) => {
  return c.json(
    {
      message: err.message,
    },
    HttpStatusCodes.INTERNAL_SERVER_ERROR,
  );
});

app.openapi(
  createRoute({
    path: "/",
    tags: ["Index"],
    description: "Index route",
    method: "get",
    responses: {
      [HttpStatusCodes.OK]: {
        content: {
          "application/json": {
            schema: z.object({
              message: z.string(),
            }),
          },
        },
        description: "Index route",
      },
    },
  }),
  (c) => {
    return c.json({ message: "Hello World" }, HttpStatusCodes.OK);
  },
);

export default app;

stoker/http-status-phrases

HTTP status phrase constants.

Example Usage

import * as HttpStatusPhrases from "stoker/http-status-phrases";

console.log(HttpStatusPhrases.NOT_FOUND); // Not Found

Middlewares

stoker/middlewares/not-found

A default 404 handler.

  • Responds with JSON object
    • Message property includes not found path
  • Sets status code to 404

Example Usage

import { Hono } from "hono";
import notFound from "stoker/middlewares/not-found";

const app = new Hono();

app.notFound(notFound);

export default app;

stoker/middlewares/on-error

A default error handler.

  • Responds with JSON object
    • Message property includes error message
    • Stack trace included when NODE_ENV !== "production"
  • Sets status code to existing status code if already set OR 500

Example Usage

import { Hono } from "hono";
import onError from "stoker/middlewares/on-error";

const app = new Hono();

app.onError(onError);

export default app;

stoker/middlewares/serve-emoji-favicon

Serve an svg emoji as a favicon from /favicon.ico

Example Usage

import { Hono } from "hono";
import serveEmojiFavicon from "stoker/middlewares/serve-emoji-favicon";

const app = new Hono();

app.use(serveEmojiFavicon("🔥"));

export default app;

Open API

Default Hook

A default error hook you can include in your OpenAPIHono instance. Includes the success status and ZodError

Example Usage

import { OpenAPIHono } from "@hono/zod-openapi";
import defaultHook from "stoker/openapi/default-hook";

/*
Any validation errors will respond with status code 422 and body:
{
  success: false,
  error: {}, // Full Zod Error
}
*/
const app = new OpenAPIHono({
  defaultHook,
});

export default app;

Helpers

stoker/openapi/helpers/json-content

Create a content / schema description with a type of application/json

Example Usage
import { z } from "@hono/zod-openapi";
import jsonContent from "stoker/openapi/helpers/json-content";

const schema = z.object({
  message: z.string(),
});

/*
* Equivalent to:
{
  content: {
    "application/json": {
      schema,
    },
  },
  description: "Retrieve the user",
}
*/
const response = jsonContent(
  schema,
  "Retrieve the message"
);

stoker/openapi/helpers/json-content-required

Useful for json body schema validators.

Create a content / schema description with a type of application/json and required set to true

Example Usage
import { z } from "@hono/zod-openapi";
import jsonContentRequired from "stoker/openapi/helpers/json-content-required";

const schema = z.object({
  message: z.string(),
});

/*
* Equivalent to:
{
  content: {
    "application/json": {
      schema,
    },
  },
  description: "Retrieve the user",
  required: true
}
*/
const response = jsonContentRequired(
  schema,
  "Retrieve the message"
);

stoker/openapi/helpers/json-content-one-of

Peer dependency of @asteasolutions/zod-to-openapi

WARNING: Not recommended right now, type hints from @hono/zod-openapi are not correct when using this helper. If you don't absolutely need oneOf in your specification, use zod or (anyOf) instead.

Create a json content / schema description where the schema can be oneOf multiple schemas. Useful when you have multiple possible validation response schemas.

Example Usage
import { z } from "@hono/zod-openapi";
import jsonContentOneOf from "stoker/openapi/helpers/json-content-one-of";
import createErrorSchema from "stoker/openapi/schemas/create-error-schema";
import IdParamsSchema from "stoker/openapi/schemas/id-params";

const bodySchema = z.object({
  name: z.string(),
});

/*
* Equivalent to:
{
  content: {
    "application/json": {
      schema: {
        oneOf: SchemaObject[]
      },
    },
  },
  description: "Invalid Id params or Invalid Body"
}
*/
const result = jsonContentOneOf(
  [createErrorSchema(IdParamsSchema), createErrorSchema(bodySchema)],
  "Invalid Id params or Invalid Body"
);

stoker/openapi/helpers/one-of

Peer dependency of @asteasolutions/zod-to-openapi

Used internally by stoker/openapi/helpers/json-content-one-of but exported here in case you need to access the generated schemas for other use cases.

import { z } from "@hono/zod-openapi";
import oneOf from "stoker/openapi/helpers/one-of";
import createErrorSchema from "stoker/openapi/schemas/create-error-schema";
import IdParamsSchema from "stoker/openapi/schemas/id-params";

const bodySchema = z.object({
  name: z.string(),
});

/*
* Returns: SchemaObject[]
*/
const result = oneOf([createErrorSchema(IdParamsSchema), createErrorSchema(bodySchema)]);

Schemas

Commonly used zod schemas for use when creating routes with @hono/zod-openapi

stoker/openapi/schemas/id-params

Validate id in path params as a number.

Example Usage
import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi";
import * as HttpStatusCodes from "stoker/http-status-codes";
import jsonContent from "stoker/openapi/helpers/json-content";
import IdParamsSchema from "stoker/openapi/schemas/id-params";

const app = new OpenAPIHono();

app.openapi(
  createRoute({
    method: "get",
    path: "/users/{id}",
    request: {
      params: IdParamsSchema,
    },
    responses: {
      [HttpStatusCodes.OK]: jsonContent(
        z.object({
          id: z.number(),
        }),
        "Retrieve the user",
      ),
    },
  }),
  (c) => {
    // id is a valid number
    const { id } = c.req.valid("param");
    return c.json({
      id,
    }, HttpStatusCodes.OK);
  },
);

export default app;

stoker/openapi/schemas/slug-params

Validate slug in path params as a slug.

Example Usage
import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi";
import * as HttpStatusCodes from "stoker/http-status-codes";
import jsonContent from "stoker/openapi/helpers/json-content";
import SlugParamsSchema from "stoker/openapi/schemas/slug-params";

const app = new OpenAPIHono();

app.openapi(
  createRoute({
    method: "get",
    path: "/posts/{slug}",
    request: {
      params: SlugParamsSchema,
    },
    responses: {
      [HttpStatusCodes.OK]: jsonContent(
        z.object({
          slug: z.string(),
        }),
        "Retrieve the post",
      ),
    },
  }),
  (c) => {
    // slug is a valid slug
    const { slug } = c.req.valid("param");
    return c.json({
      slug,
    }, HttpStatusCodes.OK);
  },
);

export default app;

stoker/openapi/schemas/id-uuid-params

Validate id in path params as a uuid.

Example Usage
import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi";
import * as HttpStatusCodes from "stoker/http-status-codes";
import jsonContent from "stoker/openapi/helpers/json-content";
import IdUUIDParamsSchema from "stoker/openapi/schemas/id-uuid-params";

const app = new OpenAPIHono();

app.openapi(
  createRoute({
    method: "get",
    path: "/users/{id}",
    request: {
      params: IdUUIDParamsSchema,
    },
    responses: {
      [HttpStatusCodes.OK]: jsonContent(
        z.object({
          id: z.uuid(),
        }),
        "Retrieve the user",
      ),
    },
  }),
  (c) => {
    // id is a valid uuid
    const { id } = c.req.valid("param");
    return c.json({
      id,
    }, HttpStatusCodes.OK);
  },
);

export default app;

stoker/openapi/schemas/get-params-schema

Validate a custom named path param using Zod string validators by calling the function getParamsSchema({ name, validator }).

Name defaults to id. Validator defaults to uuid and supports type "uuid" | "nanoid" | "cuid" | "cuid2" | "ulid".

Example Usage
import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi";
import * as HttpStatusCodes from "stoker/http-status-codes";
import jsonContent from "stoker/openapi/helpers/json-content";
import getParamsSchema from "stoker/openapi/schemas/get-params-schema";

const app = new OpenAPIHono();

app.openapi(
  createRoute({
    method: "get",
    path: "/users/{userId}",
    request: {
      params: getParamsSchema({
        name: "userId",
        validator: "nanoid",
      }),
    },
    responses: {
      [HttpStatusCodes.OK]: jsonContent(
        z.object({
          userId: z.nanoid(),
        }),
        "Retrieve the user",
      ),
    },
  }),
  (c) => {
    // userId is a valid nanoid
    const { userId } = c.req.valid("param");
    return c.json({
      userId,
    }, HttpStatusCodes.OK);
  },
);

export default app;

stoker/openapi/schemas/create-message-object

Create an object schema with a message string property. Useful for error messages.

Example Usage
import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi";
import * as HttpStatusCodes from "stoker/http-status-codes";
import * as HttpStatusPhrases from "stoker/http-status-phrases";
import jsonContent from "stoker/openapi/helpers/json-content";
import createMessageObjectSchema from "stoker/openapi/schemas/create-message-object";
import IdParamsSchema from "stoker/openapi/schemas/id-params";

const app = new OpenAPIHono();

app.openapi(
  createRoute({
    method: "get",
    path: "/some-thing-that-might-not-be-found",
    responses: {
      [HttpStatusCodes.NOT_FOUND]: jsonContent(
        createMessageObjectSchema(HttpStatusPhrases.NOT_FOUND),
        HttpStatusPhrases.NOT_FOUND,
      ),
    },
  }),
  (c) => {
    return c.json({
      message: HttpStatusPhrases.NOT_FOUND,
    }, HttpStatusCodes.NOT_FOUND);
  },
);

export default app;

stoker/openapi/schemas/create-error-schema

Create an example error schema with zod error / validation messages based on given schema.

Example Usage
import { createRoute, z } from "@hono/zod-openapi";
import * as HttpStatusCodes from "stoker/http-status-codes";
import jsonContent from "stoker/openapi/helpers/json-content";
import createErrorSchema from "stoker/openapi/schemas/create-error-schema";

const TaskSchema = z.object({
  name: z.string(),
  completed: z.boolean().default(false),
});

export const createTask = createRoute({
  method: "post",
  path: "/task",
  request: {
    body: jsonContent(TaskSchema, "The Task"),
  },
  responses: {
    // ... OK response here
    [HttpStatusCodes.UNPROCESSABLE_ENTITY]: jsonContent(
      // Creates example schema with validation messages for name / completed
      createErrorSchema(TaskSchema),
      "Invalid task",
    ),
  },
});

Credits

Project bootstrapped with antfu/starter-ts