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

@odatnurd/cf-requests

v0.1.12

Published

Simple Cloudflare Hono request wrapper

Readme

Simple CloudFlare Request Handlers

cf-requests is a simple set of wrapper functions that allow for working with requests in a Cloudflare Worker or in Cloudflare Pages using Hono as a route handler.

The focus is to allow for more easy creation of robust API routes, providing a standard JSON response format on both success and failure.

The request validation functionality is intended to be used alongside the @axel669/joker schema validation library. However this is not strictly required, and the validation wrapper can be used with any validator with some simple wrapper code; see below for more details.

The examples seen here utilize @axel669/hono-file-routes, which allows for file based routing in a Cloudflare worker. This is also not strictly required.

Finally, there are some routines here that can be used with the @axel669/aegis test runner library, which is also documented below.

Installation

Install cf-requests via npm, pnpm, and so on, in the usual way.

npm install @odatnurd/cf-requests

Usage

The library provides all of the pieces needed to:

  • validate incoming requests based on a schema (applied to the json body, path parameters, query parameters, result body, etc). In a
  • return a consistently structured json result in both success and failure conditions
  • wrap a request handler with exception handling to remove boilerplate and help enforce consistency in results.
  • compose all of the above into a single, cohesive handler for a route.

Example

This example presumes that the @axel669/joker library is being used to implement schema validation, and that routes are defined via the @axel669/hono-file-routes package.

The contents of the test_input.joker.json file looks like the following, defining that the body of the request contain key1 and key2, each of specific types:

{
  "itemName": "body",
  "root": {
    "key1": "number",
    "key2": "string",
  }
}

Similarly, the contents of test_output.joker.json looks like the following, which describes what the result of the request should look like:

{
  "itemName": "result",
  "root": {
    "success": "bool",
    "status": "number",
    "data": {
      "key1": "number",
      "key2": "string",
    }
  }
}

Given this, the following is a minimal route handler that validates the JSON body against the schema and verifies that the response being sent out is as expected.

The request will result in a 422 Unprocessible Entity error if the input data is not valid, a 500 Internal Server Error if the response body is not valid, and the JSON body is masked to ensure that only the fields declared by the schema are present if all goes well.

// Bring in the validation generator, the success response generator, and the
// route handler generator.
import { validate, success, routeHandler } from '@odatnurd/cf-requests';

// Using the Joker rollup plugin, this will result in a objects with a
// `validate()` and `mask()` function within them, which verify that the result
// is correct and mask away any fields not defined by the schema, respectively.
import * as inputSchema from '#schemas/test_input';
import * as outputSchema from '#schemas/test_output';

// The hono-file-routes package defines routes in a file by exporting `$verb`
// as routes. Here we are using the routeHandler() generator, which constructs
// an appropriate route array based on its arguments.
//
// This value could also be used in a standard Hono app.
export const $post = routeHandler(
  // Generate a validator that verifies the body of the request is json that
  // matches the schema; results in a 422 error otherwise.
  validate('json', inputSchema),

  // Generator a validator that verifies that the result being sent back to the
  // client matches the schema; results in a 500 error otherwise.
  validate('result', outputSchema),

  // Async functions that take a single argument are route handlers; they will
  // be automatically guarded with a try/catch block
  async (ctx) => {
    // PUll out the validated JSON body.
    const body = ctx.req.valid('json');

    // Thrown exceptions inside the handler cause a fail() call to occur; the
    // status is 500 for generic errors, but you can throw HTTPError instances
    // to get a specific result as desired.
    if (body.key1 != 69) {
      throw new Error('key is not nice');
    }

    return success(ctx, 'request body validated', body);
  },
);

Testing Utilities (Optional)

This package includes an optional set of helpers to facilitate testing your own projects with the @axel669/aegis test runner and the @odatnurd/cf-aegis helper libraries.

To use these utilities, you must install the required peer dependencies into your own project's devDependencies if you have not already done so.

pnpm add -D @axel669/aegis @axel669/joker @odatnurd/cf-aegis miniflare

The @odatnurd/cf-requests/aegis module exports the following functions to aid in setting up tests:

Aegis Helper Functions

export function initializeRequestChecks() {}

Registers all custom checks with Aegis. This should be called once at the top of your aegis.config.js file.


export async function schemaTest(dataType, schema, data, validator = undefined) {}

Takes a dataType and schema as would be provided to the validate function and runs the validation against data to see what the result is. The function will return either:

  • Valid Data: An Object that represents the validated and masked data
  • Invalid Data: A Response object that carries the error payload

Using this, it is possible to validate that a schema works as expected without having to use it in the actual request first.

ℹ️ By default, the test will use the validate function to perform the data validation. If desired, you can pass an optional validator function as the final argument. This must take the same arguments as validate does, and follow the same contract. This allows for testing of other schema libraries, such as during migrations to this library.

Aegis Test Configuration

You can import the helper functions into your aegis.config.js file to easily set up a test environment, optionally also populating one or more SQL files into the database first in order to set up testing.

Example aegis.config.js:

import { initializeCustomChecks, aegisSetup, aegisTeardown } from '@odatnurd/cf-aegis';
import { initializeRequestChecks } from '@odatnurd/cf-requests/aegis';

initializeCustomChecks();
initializeRequestChecks()

export const config = {
    files: [
        "test/**/*.test.js",
    ],
    hooks: {
        async setup(ctx) {
            await aegisSetup(ctx, 'test/setup.sql', 'DB');
        },

        async teardown(ctx) {
            await aegisTeardown(ctx);
        },
    },
    failAction: "afterSection",
}

Custom Checks

The initializeRequestChecks() function registers several custom checks with Aegis to simplify testing database-related logic.

  • .isResponse($): Checks if a value is a Response object.
  • .isNotResponse($): Checks if a value is not a Response object.
  • .isResponseWithStatus($, count): Checks if an object is a Response with a specific status code.

Library Methods

export async function success(ctx, message, result=[], status=200) {}

Generate a successful return in JSON with the given HTTP status code; the status code is used to construct the JSON as well as the response object:

{
    "success": true,
    status,
    message,
    data: result
}

result is optional and defaults to an empty array if not provided. Similarly status is option and defaults to 200 if not provided.

If the validate('result') function was used to set a middleware to validate the result of the request handler, then this function will validate that the entire returned body, including result you provide matches the schema, and will also optionally mask it, if a mask function was also provided.

In such a case, if the result body does not conform to the schema, a SchemaError exception will be thrown. This is automatically handled by body(), and will result in a fail() response instead of a success().


export function fail(ctx, message, status=400, result=undefined) {}

Generate a failure return in JSON with the given HTTP status code; the status code is used to construct the JSON as well as the response object:

This results in JSON in a similar form to success, though the success field is false instead of true.

Note that the order of the last two arguments is different because generally one wants to specify the status of an error but it usually does not return any other meaningful result (which is the opposite of the success case).

If status is not provided, it defaults to 400, while if result is not provided, the data field will not be present in the result.

Note that unlike success(), fail() will not honor the addition of an output validator via validate('result') on the result, since it is usually expected that it will not provide a meaningful data result to an error response.


export function validate(dataType, { validate, mask? }) {}

This function uses the Hono validator() function to create a validator that will validate the data of the provided type using the provided validation object. In addition 'result' is also valid, and specifies that the validator should verify that the result of a call to success() from within the route handler.

The second parameter should be an object that contains a validate and an (optional) mask member:

  • validate takes the item to validate, and returns true when the data given is valid. Any other return value is considered to be an error.
  • mask takes as an input the same item passed to validate, and returns a masked version of the data that strips fields from the object that do not appear in the schema.

If mask is not provided, then the data will be validated but not masked.

This method is intended to be used with the @axel669/joker library (and in particular it's rollup plugin), though you are free to use any other validation schema so long as the call signatures are as defined above.

On success, input data is placed into the context to be retrieved by the handler or to be validated via success(), depending on the value of dataType.

On failure, the fail() method is invoked, specifying the reason for the validation failure and a status code of either 422 (input) or 500 (output).


export function body(handler) {}

This is a simple wrapper which returns a function that wraps the provided handler function in a try-catch block, so that any uncaught exceptions can gracefully return a fail() result.

The wrapper returned by this function will itself return the result of the provided handler, so long as no exceptions are raised.

When an exception is caught, the fail() function is used to generate and return a response; this will contain as a message the text of the exception that was caught.

Exceptions of type HttpError carry a specific HTTP status code, which will be used in the call to fail(); all other exceptions use a status of 500.

For debugging, if your worker has the CF_REQUESTS_STACKTRACE environment variable set to either true or yes, the fail() response will include in its data field the stack trace as an array of strings that represent the trace.

ℹ️ If the body catches a SchemaError, the CF_REQUESTS_STACKTRACE variable will be ignored since in this case it is the data and not the code that was at fail. In these cases, the result inside of the returned body will be the validation error object instead.

export const $post = [
  validate('json', testSchema),

  body(async (ctx) => {
    const body = ctx.req.valid('json');

    if (body.key1 != 69) {
      throw new Error('key is not nice');

      // The above throw is the same as:
      // return fail(ctx, 'key is not nice', 500)
    }

    return success(ctx, 'code test worked', body);
  }),
];

export class HttpError extends Error { constructor(message, status=500) {} }

This is a simple exception class that wraps a textual message and a status code.

When body() catches an exception of this type, the fail() call it makes will use the status provided here as the HTTP status of the return.

If status is not provided, it defaults to 500, making this class generate an error with the same layout as any other exception class.


export class SchemaError extends HttpError { constructor(message, status=500, result=undefined) {} }

This is a simple extension to HttpError and is thrown in cases where a schema validation error has occurred; it is handled by body() the same as HttpError is. If the exception has a result, it will be used in the call to fail() in this case, so that the result of the validation will be returned.


export function routeHandler(...args) {}

This small helper makes it slightly cleaner to set up a route handler by taking any number of arguments and returning them back as an array.

As a part of this operation, any async function that takes exactly one argument is implicitly wrapped in body(). This makes the resulting handler somewhat cleaner looking, while still allowing for arbitrary middleware

export const $post = routeHandler(
  validate('json', testSchema),

  // More than one argument, so function is directly returned; no body() call
  // wrapper here.
  async (ctx, next) => {
    console.log('Async middleware is running!');
    await next();
  },

  // Single argument async functions are wrapped in body(), so exceptions raised
  // are handled consistently.
  async (ctx) => {
    const body = ctx.req.valid('json');

    if (body.key1 != 69) {
      throw new Error('key is not nice');
    }

    return success(ctx, 'code test worked', body);
  },
);