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

@farbenmeer/tapi

v0.6.1

Published

This library makes it possible to build a Rest API including a fully typed client without a compile step.

Readme

TApi

This library makes it possible to build a Rest API including a fully typed client without a compile step.

State of this library

Very much alpha. I just came up with this idea. Currently only supports GET and POST requests.

Plans

  • Support more HTTP methods
  • Add function to automatically generate OpenAPI specs
  • Add support for server-clients for use on the server that call the handler functions directly instead of making a fetch request

Inspiration

Very much TRPC. It's quite similar but with a REST paradigm so the API is actually usable for other clients.

Setup

npm install @farbenmeer/tapi
yarn add @farbenmeer/tapi
pnpm add @farbenmeer/tapi
bun add @farbenmeer/tapi

Create a file (conventionally called api.ts) with your API definition:

import { defineApi } from "@farbenmeer/tapi/server"

export const api = defineApi()

Set up a route to handle the requests. This depends on your framework. For Next.js it would be app/api/[...tapi]/route.ts:

import { api } from "api"
import { createRequestHandler } from "@farbenmeer/tapi/server"

const handler = createRequestHandler(api, {
  basePath: "/api"
})

export const GET = handler
export const POST = handler
export const PUT = handler
export const DELETE = handler
export const PATCH = handler

Create another file (conventionally called client.ts) with your client definition:

import type { api } from "./api"
import { defineFetchClient } from "@farbenmeer/tapi/client"

export const client = defineFetchClient<typeof api>(apiUrl)

where apiUrl is the base URL of your API, usually something like https://example.com/api.

Adding Routes

Define your first route as a file, in this example api/books.ts:

import { defineHandler, TResponse } from "@farbenmeer/tapi/server"

export const GET = defineHandler({ authorize: () => true }, async () => {
  return TResponse.json([
    { id: 1, title: "TApi for Dummies" },
    { id: 2, title: "Advanced TApi" }
  ])
})

extends api.ts:

import { defineApi } from "@farbenmeer/tapi/server"

export const api = defineApi()
  .route("/books", import("./api/books"))

Authorization

The authorize argument in defineHandler allows you to authorize routes based on request headers and other request data. The authorize function receives a TRequest object that extends the standard Request, giving you access to headers, URL parameters, and query parameters for authorization decisions.

import { defineHandler, TResponse } from "@farbenmeer/tapi/server"

export const GET = defineHandler({
  authorize: (req) => {
    const authHeader = req.headers.get('Authorization')
    if (!authHeader?.startsWith('Bearer ')) {
      throw new Error('Unauthorized: Missing or invalid token')
    }

    const token = authHeader.slice(7) // Remove 'Bearer ' prefix
    // Validate token and return user data
    return validateTokenAndGetUser(token)
  }
}, async (req) => {
  // req.auth now contains the data returned from authorize
  const user = req.auth()
  return TResponse.json({ message: `Hello ${user.name}` })
})

The authorize function can return any data type, which will then be available as req.auth() in your handler. This is useful for passing user information, permissions, or other authorization context to your route handlers.

Using the client

In your client-side javascript code, you can use the client to make requests to your API. For example:

import { client } from "client"

async function fetchBooks() {
  const books = await client.books.get()
  console.log(books)
}

fetchBooks()

Dynamic Paths

Define a route with a dynamic path parameter using the params option for defineHandler, for example /api/book.ts

import { defineHandler, TResponse } from "@farbenmeer/tapi/server"
import { z } from "zod/v4"

export const GET = defineHandler({
  authorize: () => true,
  params: { id: z.string() }
}, async (req) => {
  return TResponse.json({
    id: req.params().id,
    title: `Book with id ${req.params().id}`
  })
})

extends api.ts:

import { defineApi } from "@farbenmeer/tapi/server"

export const api = defineApi()
  .route("/books", import("./api/books"))
  .route("/books/:id", import("./api/book"))

call the route on the client as

import { client } from "client"

async function fetchBook(id: string) {
  const book = await client.books[id].get()
  console.log(book)
}

fetchBook('1')

Query Parameters

Define a route with query parameters using the query option for defineHandler, for example /api/search.ts

import { defineHandler, TResponse } from "@farbenmeer/tapi/server"
import { z } from "zod/v4"

export const GET = defineHandler({
  authorize: () => true,
  query: { q: z.string() }
}, async (req) => {
  return TResponse.json({
    query: req.query().q,
    results: []
  })
})

extends api.ts:

import { defineApi } from "@farbenmeer/tapi/server"

export const api = defineApi()
  .route("/books", import("./api/books"))
  .route("/books/:id", import("./api/book"))
  .route("/search", import("./api/search"))

call the route on the client as

import { client } from "client"

async function searchBooks(q: string) {
  const books = await client.search.get({ q })
  console.log(books)
}

searchBooks('TApi')

Post Requests

Define a POST-route, for example to add a book in api/books.ts:

import { defineHandler, TResponse } from "@farbenmeer/tapi/server"
import { z } from "zod/v4"

/* export const GET = ... */

export const POST = defineHandler({
  authorize: () => true,
  body: z.object({ title: z.string() }),
}, async (req) => {
  const { title } = await req.data()
  const book = await createThisBook({ title })
  return TResponse.json(book)
})

and call it as either:

import { client } from "client"

async function addBook(title: string) {
  const book = await client.books.post({ title })
  console.log(book)
}

addBook('TApi')

or with formData:

import { client } from "client"

async function addBook(title: string) {
  const formData = new FormData()
  formData.set('title', title)
  const book = await client.books.post(formData)
  console.log(book)
}

addBook('TApi')

Wildcard Routes

Use wildcards to match arbitrary paths:

// api/files.ts
import { defineHandler, TResponse } from "@farbenmeer/tapi/server"
import { z } from "zod/v4"

export const GET = defineHandler({
  authorize: () => true,
  params: { path: z.string() }
}, async (req) => {
  const filePath = req.params().path
  // filePath = "documents/report.pdf" for /api/files/documents/report.pdf
  const file = await getFile(filePath)
  return TResponse.json(file)
})

Define the route in api.ts:

export const api = defineApi()
  .route("/files/*path", import("./api/files"))

Call it from the client:

const file = await client.files["documents/report.pdf"].get()

Note: Wildcards (* or *name) match everything including slashes and must come at the end of the path. Use *name to capture the matched portion as a parameter. which is particularly useful with react forms:

<form action={client.books.post}>
  <input name="title" />
</form>