The appRouter in Shapeless

The appRouter is the central router that handles all your backend API requests. It:

  • Routes requests to the right procedure
  • Acts as the backend entry point for deployment

Since Shapeless builds on Hono, you can deploy this router anywhere — Cloudflare Workers, Vercel, Netlify, Railway, AWS, and more.

application/
  └── resources/
      ├── shapeless.ts       # Initialize Shapeless
      ├── index.ts           # Main appRouter
      └── routers/           # API routers
          ├── post-router.ts
          ├── user-router.ts
          └──
  ────src/
      |── app

Creating your appRouter

Start by creating a base api router with global config like base path, CORS, and error handling.

resources/index.ts

import { app } from "./shapeless"
import { postRouter } from "./routers/post-router"
 
// 1. Base API with global middleware & config
const api = app
  .router()
  .basePath("/api")
  .use(app.defaults.cors)
  .onError(app.defaults.errorHandler)
 
// 2. Merge feature routers
const appRouter = app.mergeRouters(api, {
  post: postRouter,
})
 
export type AppRouter = typeof appRouter
export default appRouter

Router Configuration Options

Shapeless routers extend the Hono API, so you have full access to all Hono router features like:

  • Global error handling
  • Custom 404 responses
  • Middleware chaining
  • And more

Common options you’ll use:


Error Handling

Use app.defaults.errorHandler for standardized API error responses:

resources/index.ts

const api = app
  .router()
  .basePath("/api")
  .use(app.defaults.cors)
  .onError(app.defaults.errorHandler)

On the frontend, handle errors like this:

app/page.tsx

"use client"
 
import { useMutation } from "@tanstack/react-query"
import { HTTPException } from "hono/http-exception"
import { client } from "@/lib/shapeless-client"
 
export default function Page() {
  const { mutate: createPost } = useMutation({
    mutationFn: async () => {
      const res = await client.post.create.$post()
      return await res.json()
    },
    onError: (err: HTTPException) => {
      console.error(err.message)
    },
  })
 
  return <button onClick={() => createPost()}>Create Post</button>
}

You can also customize error handling:

api.onError((err, c) => {
  console.error(err)
  return c.text("Something went wrong", 500)
})

Base Path

Set the base path where your API lives:

const api = app
  .router()
  .basePath("/api") // All API routes will be under /api/*
  .use(app.defaults.cors)
  .onError(app.defaults.errorHandler)

Make sure your API route folder corresponds with this path.


CORS Middleware

CORS is handled by default via:

.use(app.defaults.cors)

If you want to customize CORS, use Hono’s cors middleware and include the x-is-superjson header because Shapeless uses it internally for JSON serialization:

import { cors } from "hono/cors"
 
const api = app
  .router()
  .use(
    cors({
      allowHeaders: ["x-is-superjson"],
      exposeHeaders: ["x-is-superjson"],
      origin: "*",
      credentials: true,
    }),
  )
  .onError(app.defaults.errorHandler)

Inferring Router Input and Output Types

Leverage Shapeless’s TypeScript helpers to infer procedure inputs and outputs for type-safe frontend calls:

import type { AppRouter } from "./index"
import type { InferRouterInputs, InferRouterOutputs } from "@shapelesss/core"
 
type Input = InferRouterInputs<AppRouter>
type Output = InferRouterOutputs<AppRouter>
 
// Usage example:
type CreatePostInput = Input["post"]["create"]
type CreatePostOutput = Output["post"]["create"]

This structure lets you organize your backend API cleanly, keep things type-safe end-to-end, and deploy anywhere with confidence.