Boilerplate for your typescript projects using Hono.
Project Structure
Tech Stack
Requirements
Run Locally
Manage your database using migrations
Run test
FAQ
Dependencies
Extras
The main app implementation is inside of the /app
directory where it uses basic js node implementation.
"/routes": Routes of the application.
"/lib/db": Database structure. Migrations, seed and types.
"/app/cases": Use cases of your application.
"/app/repositories": Repositores and interfaces that are used by the use cases.
"/node.ts": Initial file to run the project using node.
"/bun.ts": Initial file to run the project using bun.
Geral: Hono, Zod, Eslint
Database: Kysely (queries, migrations, types)
Test: Bun test
node.js v20+ or bun
nvm installed to manage node versions
pnpm to manage dependencies(npm install -g pnpm)
📁 Setup your database
I recommend using dbngin to spin up an local DB on your machine.
[!NOTE]
If you prefer docker, you can use postgres service from this docker compose
Create your database
CREATE DATABASE project
Create a .env
files from .env.example
and populate the values.
cp .env.example .env
Nodejs
nvm use
pnpm install
Bun
bun install
Nodejs
pnpm node:dev or pnpm dev
Bun
pnpm bun:dev
From here you should be getting a server running on http://localhost:3333
Migrations are currently defined under lib/db/migrations
. An initial migration is already there as an example, adjust to meet your project requirements. Reference
Run all migrations
pnpm db:migrate:up
This command will perform the "up" function for all new migrations
Rollback previous migration
pnpm db:migrate:down
This command will perform the "down" function from previous migration
Run seed
pnpm db:seed
Reset migrations + run seed
pnpm db:reset
To make an update on the database you will need to create a migration
Run the command
pnpm db:migrate:create
This will generate a new file under /lib/db/migrations/DATE-initial.ts
-
Rename the file to describe what the migration will do e.g
DATE-adding_phone_column_to_user.ts
-
Functions up and down should work.
This project is using kanel to generate types, it handles better types and enums when using with postgres.
If you need to use a different database, I recommend kysely-codegen instead.
Tests are implemented using bun which follows a jest-compatible structure.
pnpm test
Reference: https://bun.sh/docs/cli/test#run-tests
Why this structure?
This is a matter of personal preference and depends on your application and deployment process.
I've been using this case structure for a while and have found it enjoyable, though I'm still improving and learning as I go.
I often aim for a balanced approach to structure for various reasons.
As a personal recommendation, try not to become too attached to any one framework. You’ll gain more value by focusing on structuring your code and learning about patterns that can benefit your team, projects, and clients.
Feel free to adapt these ideas to fit your needs.
Framework agnostic?
Thanks to Hono's simplicity, you can structure your project in a way that suits your needs.
The core of this project is located in the /app directory, where I use only JavaScript; none of these files are specific to Hono. This means that if you ever need to switch away from Hono for any reason, you can simply copy the /app directory and adjust the request handling as needed.
Bun or node?
Thanks to this structure, I can easily switch between them for testing. However, I still recommend using Node.js.
It really depends on your project and situation. I haven’t had the chance to test it in a large-scale real-world project yet, so I can’t say for sure. However, Bun is expected to be more efficient and use less memory.
Currently, I'm using Bun to run my tests, and it works well since it’s based on Jest.
Why hono?
Based on my experience with Express.js and Fastify, I’ve found Hono to be powerful, easy to use, and supported by an active community.
Give it a try.
Here are some basic benchmarks (though they’re not particularly significant).
Requests benchmark
Compare benchmark
If you're still not convinced, Fastify is also an excellent option.
Nodejs
To run the project using nodejs, we need some extra dependencies. These are already set in the project.
// dependencies
@hono/node-server
// devDependencies
typescript
tsx
- For the setup you can use the hono middleware created for that, you can follow the instructions on the readme there.
The setup is basically adding the middleware on the initial file.
...
import { sentry } from '@hono/sentry'
...
app.use(
'*',
sentry({
dsn: process.env.SENTRY_DNS,
tracesSampleRate: isProduction ? 0.2 : 0.8,
environment,
}),
)
Then you can call on your global app.onError
app.onError((error, c) => {
c.get('sentry').captureException(e, {
tags: {}, // any tag
extra: {}, // any extra object
})
return c.json({ error, message: error.message || 'Unknown Error' }, 500)
})