First Steps

Shapeless helps you build fast, modern, fullstack apps with Next.js 15, Hono, and type-safe APIs—all optimized for Cloudflare Workers, Neon, and Drizzle ORM.

There are two ways to get started:

  • Using the CLI (recommended)
  • Manual setup (advanced)

If you used the CLI, everything is already set up. This guide walks through the core building blocks so you understand how your app works under the hood.


🗂️ Project Structure

Here’s the core file structure you'll see inside the resources/ directory of a Shapeless project:

resources/
├── db/
   └── schema.ts          # Drizzle schema
├── index.ts               # Main app router
├── routers/
   └── post-router.ts     # Example router
├── shapeless.ts           # Framework + middleware setup
├── shared/
   └── providers.tsx      # React Query provider
└── templates/
    └── post.tsx           # Sample UI or render component

This file sets up your app context, environment, and database middleware. It's the foundation of your backend API.

resources/shapeless.ts

import { neon } from "@neondatabase/serverless"
import { drizzle } from "drizzle-orm/neon-http"
import { env } from "hono/adapter"
import { shapeless } from "@shapelesss/core"
 
interface Env {
  Bindings: { DATABASE_URL: string }
}
 
export const j = shapeless.init<Env>()
 
export const databaseMiddleware = j.middleware(async ({ c, next }) => {
  const { DATABASE_URL } = env(c)
  const sql = neon(DATABASE_URL)
  const db = drizzle(sql)
  return await next({ db })
})
 
export const publicProcedure = j.procedure.use(databaseMiddleware)

Use publicProcedure to build any query/mutation with database access.


Routers group related procedures. In this example, postRouter has two procedures: recent and create.

resources/routers/post-router.ts

import { z } from "zod"
import { j, publicProcedure } from "../shapeless"
 
interface Post {
  id: number
  name: string
}
 
const posts: Post[] = [{ id: 1, name: "Hello World" }]
 
export const postRouter = j.router({
  recent: publicProcedure.query(({ c }) => {
    return c.superjson(posts.at(-1) ?? null)
  }),
 
  create: publicProcedure
    .input(z.object({ name: z.string().min(1) }))
    .mutation(({ c, input }) => {
      const post: Post = { id: posts.length + 1, name: input.name }
      posts.push(post)
      return c.superjson(post)
    }),
})
  • recent returns the latest post
  • create adds a new post with input validation via Zod

This file merges all routers and applies global middleware like CORS and error handling.

resources/index.ts

import { j } from "./shapeless"
import { postRouter } from "./routers/post-router"
 
const api = j
  .router()
  .basePath("/api")
  .use(j.defaults.cors)
  .onError(j.defaults.errorHandler)
 
const appRouter = j.mergeRouters(api, {
  post: postRouter,
})
 
export type AppRouter = typeof appRouter
export default appRouter

All routes inside /api/post/* will now be handled by postRouter.


Shapeless encourages React Query for frontend API calls. Wrap your app with this provider:

resources/shared/providers.tsx

"use client"
 
import {
  QueryCache,
  QueryClient,
  QueryClientProvider,
} from "@tanstack/react-query"
import { HTTPException } from "hono/http-exception"
import { PropsWithChildren, useState } from "react"
 
export const Providers = ({ children }: PropsWithChildren) => {
  const [queryClient] = useState(
    () =>
      new QueryClient({
        queryCache: new QueryCache({
          onError: (err) => {
            if (err instanceof HTTPException) {
              // global error handling
            }
          },
        }),
      }),
  )
 
  return (
    <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
  )
}

5. Hook It Up in Next.js

To connect everything to your Next.js app, create a route handler at:

app/api/[[...route]]/route.ts

import appRouter from "@/resources"
import { handle } from "hono/vercel"
 
export const GET = handle(appRouter.handler)
export const POST = handle(appRouter.handler)

For Cloudflare Workers, this setup is handled by OpenNext. Just build and deploy:

pnpm run cf:build
pnpm run cf:upload

✅ What’s Next

You're now ready to:

  • Build more routers
  • Define your Drizzle schema in db/schema.ts
  • Deploy to Cloudflare with one command
  • Make type-safe API calls using fetch() or React Query

Use this as your base, and expand however you want—admin studio, workflows, dashboard tools, auth systems—all built on this foundation.

→ Continue: Making API Requests