API foundations
Create a Hono Node project
Scaffold a TypeScript API on Node.js with Hono and run the local development server.
Hono is a small web framework built on the standard Request and Response objects, the same ones you use with fetch() in the browser. That choice is why I picked it for this course. The same app code runs on Node.js, Bun, Deno and Cloudflare Workers. Only a thin adapter changes.
Let’s create the project. The official generator asks a few questions, and the one that matters is the template. Pick nodejs:
npm create hono@latest books-api
# Select the nodejs template
cd books-api
npm install
npm run dev
The dev server prints the address it listens on:
Server is running on http://localhost:3000
Open a second terminal and request it with curl -i. You should see HTTP/1.1 200 OK and a Hello Hono! body. That’s the whole stack working: TypeScript compiled, Node adapter listening, Hono routing the request.
What the generator created
Take a minute to look around before changing anything. package.json holds the dependency versions and the dev script. Read the versions from there, not from any tutorial, this lesson included. The generator always installs a current release.
The entry point is src/index.ts. Stripped down, it looks like this:
import { serve } from '@hono/node-server'
import { Hono } from 'hono'
const app = new Hono()
app.get('/', c => c.text('Hello Hono!'))
serve({ fetch: app.fetch, port: 3000 })
Two things live in this file, and I want you to notice they are different. The app is the Hono instance. It owns the routes and turns a Request into a Response. The adapter is serve() from @hono/node-server. It owns the TCP socket, the port, and the shutdown lifecycle.
Keep the app and the adapter apart
My advice is to split them into two files right away. Put the app in src/app.ts and export it. Keep serve() in src/index.ts, which imports the app.
The reason shows up in the testing module. Hono apps expose app.request(), which sends a Request straight into the router without opening a socket. If the app file also calls serve(), importing it in a test starts a real server. With the split, tests import app.ts and never touch the network.
When the first request fails
If npm run dev starts but curl gets nothing back, don’t reinstall dependencies. Three things go wrong, and each has a different fix.
A TypeScript error shows up in the dev terminal before any server line, so read that terminal first. A port mismatch means the server printed a different port than the one you curled, often because 3000 was busy. A handler error returns a 500 with a stack trace in the terminal, which means the server is fine and your code is not.
Before the next lesson, find the exported Hono app, the Node adapter entry point, and tsconfig.json. Knowing where each piece lives makes every later change smaller.
Lesson completed