API Routes
TMA.sh lets you add server-side logic to your Mini App by creating a Hono app in server/api/index.ts. At build time, TMA.sh detects this file, bundles it with esbuild, and deploys it to Cloudflare Workers for Platforms. Your API becomes available at {project}--api.tma.sh/api/*.
Quick start
Section titled “Quick start”Create a server/api/index.ts file in the root of your project:
import { Hono } from 'hono';
const app = new Hono();
app.get('/api/health', (c) => c.json({ status: 'ok' }));
app.post('/api/items', async (c) => { const body = await c.req.json(); return c.json({ created: true, item: body });});
export default app;Deploy as usual with tma deploy. TMA.sh will detect the API entry point and bundle it automatically alongside your static assets.
Your static SPA is served at {project}.tma.sh and your API at {project}--api.tma.sh/api/*. Same-origin /api/* requests on {project}.tma.sh are also proxied to the API worker, so you can use relative paths like /api/health from your client-side code.
Available bindings
Section titled “Available bindings”API routes run on Cloudflare Workers and have access to the following bindings:
- KV — a per-project KV namespace, provisioned automatically
- Environment variables — secrets and configuration set via the dashboard or
tma env
Use TypeScript generics to get typed access to bindings:
import { Hono } from 'hono';
type Env = { Bindings: { KV: KVNamespace; MY_SECRET: string; DATABASE_URL: string; };};
const app = new Hono<Env>();
app.get('/api/config', async (c) => { const cached = await c.env.KV.get('app-config'); if (cached) { return c.json(JSON.parse(cached)); }
const config = { version: '1.0.0' }; await c.env.KV.put('app-config', JSON.stringify(config), { expirationTtl: 3600, });
return c.json(config);});
export default app;External databases
Section titled “External databases”Per-project D1 is not available in the current release. For relational data, connect to an external database from your API routes:
- Supabase — use the Supabase client with your project’s JWT (see Authentication)
- Turso — use
@libsql/clientwith a database URL and auth token stored as environment variables - PlanetScale — use
@planetscale/databasewith a connection string
import { Hono } from 'hono';import { createClient } from '@libsql/client';
type Env = { Bindings: { TURSO_URL: string; TURSO_AUTH_TOKEN: string; };};
const app = new Hono<Env>();
app.get('/api/users', async (c) => { const db = createClient({ url: c.env.TURSO_URL, authToken: c.env.TURSO_AUTH_TOKEN, });
const result = await db.execute('SELECT * FROM users LIMIT 50'); return c.json(result.rows);});
export default app;Middleware
Section titled “Middleware”Hono’s middleware works as expected. Common patterns include CORS, logging, and authentication:
import { Hono } from 'hono';import { cors } from 'hono/cors';
const app = new Hono();
app.use('/api/*', cors({ origin: ['https://myapp.tma.sh'], allowMethods: ['GET', 'POST', 'PUT', 'DELETE'],}));
app.get('/api/data', (c) => c.json({ hello: 'world' }));
export default app;Limits
Section titled “Limits”| Resource | Limit |
|---|---|
| Bundle size | 10 MB |
| Request body | 100 MB |
| Execution time | 30 seconds |
| Subrequests | 50 per request |
Route organization
Section titled “Route organization”For larger APIs, use Hono’s route grouping:
import { Hono } from 'hono';import { users } from './routes/users';import { items } from './routes/items';
const app = new Hono();
app.route('/api/users', users);app.route('/api/items', items);
export default app;All routes imported from subdirectories will be included in the esbuild bundle automatically. Only server/api/index.ts is used as the entry point.