Build a REST API with Hono, handle routes and path parameters, and connect to a Turso database
On this page
Introduction
A web server can be configured as a resource server accessed through a set of “external” functions or endpoints (API).
REST is a fairly common way of interacting between client applications and services using HTTP.
An HTTP request consists of a method and a “path”.
By default, browsers use the GET method, and when you navigate to a website you do so with a GET request and only need to specify the “path”.
But a web API uses other methods, and clients use these methods to perform actions other than obtaining resources.
Work Environment
Create an application (with the netlify template) as explained at Hono - HTML page.
Modify the index.ts file to return a JSON document at the path /api/hello.
app.get('/api/hello', (c) => { return c.json({ ok: true, message: 'Hello Hono!', })})Run the server in development mode:
netlify devInstall curlie:
scoop install curlieMake a GET request:
curlie localhost:8888/api/helloYou can see that the server returns the JSON response you programmed:
HTTP/1.1 200 OKContent-Length: 35Content-Type: application/json
{ "ok": true, "message": "Hello Hono!"}Routes
A web API consists of different “external” functions defined through a “path”.
Path Parameters
You can use part of the path to identify the function and the other part of the path to define the input parameters.
For example, if you have a function that shows the profile of each employee, you can use the path /employee/:id, where /employee is the external name of the function and /:id is the function argument.
const employees = [{name: "David"}, {name: "Dora"}]
app.get('/employee/:id', (c) => { const {id} = c.req.param() const index = parseInt(id) const employee = employees[index] if (!employee) { return c.json('Employee not found', 404) } else { return c.json(employee) }})Path Examples
app.get('/student/:username', (c) => { const {username} = c.req.param() return c.json({"student": username})})curl http://127.0.0.1:8888/student/evaGetting a path parameter, URL query value, and appending a Response header is written as follows:
app.get('/post/:id', (c) => { const page = c.req.query('page') const id = c.req.param('id') c.header('X-Message', 'Hi!') return c.text(`You want to see ${page} of ${id}`)})We can easily handle POST, PUT, and DELETE not only GET.
app.post('/post/', (c) => c.text('Created!', 201))app.delete('/post/:id', (c) => c.text(`${c.req.param('id')} is deleted!`))Database
Use Turso to connect to a database.
Install the serverless client:
bun add @tursodatabase/serverlessCreate a .env file in the root directory of your project:
TURSO_URL=...TURSO_TOKEN=...Use the env() function to retrieve your database credentials from the environment:
import { env } from 'hono/adapter'import { createClient } from '@tursodatabase/serverless/compat'
app.get('/api/birds', async (c) => { const { TURSO_URL, TURSO_TOKEN } = env<{ TURSO_URL: string, TURSO_TOKEN: string }>(c) const client = createClient({ url: TURSO_URL, authToken: TURSO_TOKEN })
const result = await client.execute("SELECT * FROM birds") return c.json(result.rows)})Implementation Examples
Create a database table:
CREATE TABLE birds ( id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL);Get a bird by name:
app.get('/api/bird/:name', describeRoute({description: 'Get a bird by name'}), async (c) => { const {name} = c.req.param() const {TURSO_URL, TURSO_TOKEN} = env<{ TURSO_URL: string, TURSO_TOKEN: string }>(c) const client = createClient({url: TURSO_URL, authToken: TURSO_TOKEN})
const result = await client.execute("SELECT * FROM birds WHERE name = ?", [name]) if (!result.rows[0]) { return c.notFound() } return c.json(result.rows[0]) })
app.post('/api/bird/', describeRoute({description: 'Add a bird'}), async (c) => { const {name} = await c.req.json() const {TURSO_URL, TURSO_TOKEN} = env<{ TURSO_URL: string, TURSO_TOKEN: string }>(c) const client = createClient({url: TURSO_URL, authToken: TURSO_TOKEN})
await client.execute("INSERT INTO birds (name) VALUES (?)", [name]) return c.json({ ok: true, message: 'Bird added' }) })Netlify
Deploy the project to Cloud - Netlify.
netlify deploy --prodCI/CD
Deployment can be automated using a GitLab CI/CD pipeline that deploys to Netlify on every push to main.
Add a .gitlab-ci.yml file to the root of your project:
deploy: stage: deploy image: node:lts script: - npm install -g netlify-cli - bun install - bun run build - netlify deploy --prod --dir dist --auth $NETLIFY_AUTH_TOKEN --site $NETLIFY_SITE_IDSet the following variables in your GitLab project under Settings → CI/CD → Variables:
| Variable | Description |
|---|---|
NETLIFY_AUTH_TOKEN | Your Netlify personal access token |
NETLIFY_SITE_ID | The ID of the Netlify site to deploy to |
You can find your site ID in the Netlify dashboard under Site configuration → General → Site ID.
Generate a personal access token at User settings → OAuth → Personal access tokens.
OpenAPI
OpenAPI it’s an open standard for describing your APIs, allowing you to provide an API specification encoded in a JSON or YAML document.
It provides a comprehensive dictionary of terms that reflects commonly-understood concepts in the world of APIs, embedding the fundamentals of HTTP and JSON.
hono-openapi is a middleware which enables automatic OpenAPI documentation generation for your Hono API by integrating with validation libraries like Zod, Valibot, ArkType, and TypeBox.
Install the package along with your zod validation library and its dependencies:
bun add hono-openapi @hono/zod-validator zod zod-openapiNext, define a validation schema for the bird type:
import z from 'zod'
const birdSchema = z.object({ name: z.string()})Use describeRoute for route documentation and validation:
import { describeRoute } from 'hono-openapi'
app.post('/api/bird', describeRoute({ description: 'Add a bird', requestBody: birdSchema}), async (c) => {
})Add an endpoint for your OpenAPI document:
import { openAPISpecs } from 'hono-openapi'
app.get( '/api/openapi', openAPISpecs(app, { documentation: { info: { title: 'Earth API', version: '1.0.0', description: 'Earth API', }, servers: [ { url: 'http://localhost:8787', description: 'Local Server' }, ], }, }))Now, you can access the OpenAPI specification by visiting http://localhost:8080/api/openapi
You can use this specification to generate client libraries, documentation, and more.
Scalar
Scalar example:
bun add @scalar/hono-api-referenceSet up Zod OpenAPI Hono and pass the configured URL to the Scalar middleware:
// // Use the middleware to serve the Scalar API Reference at /scalarapp.get( "/api", Scalar({ url: "/api/openapi", }))