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 🙏

© 2024 – Pkg Stats / Ryan Hefner

@ohm-vision/next-apiroute

v1.0.2

Published

Wrapper for NextJS App Router API routes

Downloads

9

Readme

next-apiroute

Wrapper for NextJS App Router API routes

This wrapper will perform standard basic validation and protection steps for your API routes to standardize your control flow

Note: This library works only for Next API's using the App Router (v13+)

npm version

"Buy Me A Coffee"

Installation

Run the following command

npm install @ohm-vision/next-apiroute

Usage

Simply import the ApiRoute into your route.ts file and assign it to the standard GET, POST, PUT, PATCH or DELETE operations supported by NextJS

The library uses yup to validate the request objects before they hit your handler

Important notes

  • Each step checks if the request received an aborted signal and will shortcircuit with a 418_IM_A_TEAPOT http status code
  • If a HttpError type error is thrown (any object with a statusCode property), that value will be returned to the client
  • If the request is aborted by the client, a 418_IM_A_TEAPOT status is returned
  • If a ValidationError object is thrown, a 400_BAD_REQUEST status is returned along with the error in the body
  • All other errors, result in a 500_INTERNAL_ERROR status code and the details are logged

Order of execution

  1. readSession
    1. If validate.sessionSchema is provided, validate the session object, logs and sets session to null if it fails
  2. If secure is true, validate that the session is not null or return 401_UNAUTHORIZED
  3. If validate.paramsSchema is provided, validate the request params object, if fails, returns a 400_BAD_REQUEST along with the accompanying yup ValidationError object in the body
  4. If validate.searchSchema is provided, validate the NextRequest.nextUrl.searchParams object, if fails, returns a 400_BAD_REQUEST along with the accompanying yup ValidationError object in body
  5. readBody
    1. If validate.bodySchema is provided, validate the request body, if fails, returns a 400_BAD_REQUEST along with the accompanying yup ValidationError object in the body
  6. isAuthorized, if this returns false a 403_FORBIDDEN response is returned
  7. isValid, if this returns false a 400_BAD_REQUEST response is returned
  8. handler, fires business logic
    1. If the result is a NextResponse or Response object, it is passed back to the client (use this when passing back Blob, redirects, files or other special types of responses)
    2. If the result is null, an empty 200_OK response is returned
    3. If the result is a string, the string is returned in the body along with a 200_OK response
    4. Otherwise:
      1. The response object is converted to a NextJS-compatible object via JSON.parse(JSON.stringify(obj)),
      2. The response object is recursively stripped of all properties starting with an underscore (_)
      3. A 200_OK status code is returned along with the mutated response body

Props

The props below outline how you can configure each request

  • name (string): gives a name to the API when logging
  • secure (boolean): returns an 401_UNAUTHORIZED http status code to the client if no session is found (defaults to true)
  • log (ILogger): A logger interface for capturing messages in flight
  • validate (object)
    • sessionSchema: A yup object schema used to validate the session
    • paramsSchema: A yup object schema used to validate the path params
    • searchSchema: A yup object schema used to validate the querystring
    • bodySchema: A yup object schema used to validate the request body
  • readBody(req: NextRequest): The async function to read the request body, usually this will be req => req.json()
  • readSession(req: NextRequest): The async function to decode and resolve the current user session
  • isAuthorized(props: DecodedProps): Additional async authorization functions, maybe to verify the user is allowed to access the resource, returning false here will return a FORBIDDEN http status code
  • isValid(props: DecodedProps): Additional async validation functions, maybe to check if the resource is valid - returning false here will return in a 400_BAD_REQUEST http status code
  • handler(props: DecodedProps): Async function for the actual business logic

Example

import { ApiRoute } from "@ohm-vision/next-apiroute";
import { mixed, number, object, string, InferType, date, array } from "yup";

// @/models/sessions/session.model.ts
const permissions = [
    "read", "write"
] as const;
type Permission = typeof permissions[number];

const sessionSchema = object({
    userName: string().required().nullable(),
    expiry: date().max(new Date()).required().nullable(),
    permissions: array(mixed<Permission>().oneOf(permissions))
});

type Session = InferType<typeof sessionSchema>;

// @/models/blogs/search-blogs.dto.ts
const searchBlogsDtoSchema = object({
    search: string().nullable(),
    limit: number().min(1).max(10).nullable(),
});

// @/app/api/blogs/[type]/route.ts
const types = [
    "red", "orange", "yellow"
] as const;

type TypeParam = typeof types[number];

const name = "MyApi";

export const GET = ApiRoute({
    name: name,
    secure: false,
    log: console,
    readSession: async (req) => {
        // todo: plug into session resolver
        const session: Session = null;
        return session;
    },
    validate: {
        paramsSchema: object({
            type: mixed<TypeParam>().oneOf(types).required()
        }) ,
        searchSchema: searchBlogsDtoSchema,
        sessionSchema: sessionSchema,
    },
    handler: async ({ params: { type }, searchParams: { search, limit }}) => {
        const result = [];
        // todo: look up the blogs
        return result;
    }
});

export const POST = ApiRoute({
    name: name,
    secure: true,
    log: console,
    readSession: async (req) => {
        // todo: plug into session resolver
        const session: Session = null;
        return session;
    },
    readBody: req => req.json(),
    validate: {
        bodySchema: object({
            title: string().required().nullable(),
        }),
        paramsSchema: object({
            type: mixed<TypeParam>().oneOf(types).required()
        }),
        sessionSchema: sessionSchema,
    },
    // additional permission checking
    isAuthorized: async ({ session }) => session.permissions.includes("write"),
    // additional validation
    isValid: async ({ body, params }) => {
        // todo: additional validation
        return true;
    },
    handler: async ({
        body: { title }, params: { type }
    }) => {
        // todo: save to db
        return "works";
    }
});

Contact Me

Ohm Vision, Inc