A small REST API built while learning Hono, TypeScript, PostgreSQL, Drizzle ORM, Zod, and API authentication.
This project is primarily a learning project, not a production-ready API. The main goal was to understand how the different pieces of a modern TypeScript backend fit together.
- TypeScript — application language
- Hono — lightweight web framework
- Node.js — runtime
- PostgreSQL — database
- Drizzle ORM — database access and schema definition
- Drizzle Kit — database migrations/schema management
- Zod — request validation
- tsx — running TypeScript directly during development
- Docker — running PostgreSQL locally
Hono is designed around Web Standards and supports multiple JavaScript runtimes, including Node.js.
The project was created to understand the basic flow of a backend application:
Client
|
| HTTP Request
v
Hono Route
|
v
Validation (Zod)
|
v
Business Logic
|
v
Drizzle ORM
|
v
PostgreSQL
For authenticated requests, authentication is added to this flow:
Client
|
| Request + Authentication
v
Hono
|
v
Authentication
|
| valid
v
Route
|
v
Validation
|
v
Database
The project also helped me understand how Hono routes can be separated into different route modules and mounted onto the main application. Hono supports this kind of routing through app.route().
The project is roughly organized around the following idea:
my-app/
│
├── src/
│ ├── index.ts
│ │
│ ├── data/
│ │ └── env.ts
│ │
│ ├── routes/
│ │ └── auth.ts
│ │
│ └── ...
│
├── drizzle.config.ts
├── package.json
├── tsconfig.json
├── docker-compose.yml
├── .env
└── ...
The main entry point of the application.
This is where the Hono application is created and routes are registered.
Conceptually:
const app = new Hono()
app.route('/authors', authRoutes)The important thing to remember is:
index.tsis the place where the application is assembled.
Contains the API routes.
Instead of putting every endpoint inside index.ts, routes can be separated into modules.
For example:
const authRoutes = new Hono()
authRoutes.get(...)
authRoutes.post(...)
export default authRoutesThen the main application can mount them:
app.route('/authors', authRoutes)This keeps the entry point smaller as the application grows.
Used for environment-related configuration.
The basic idea is:
.env
↓
environment variables
↓
application configuration
↓
database / other services
Secrets such as database credentials should not be hardcoded into TypeScript files.
The project uses PostgreSQL.
PostgreSQL can be started locally using Docker.
The application connects to PostgreSQL through Drizzle ORM.
The general flow is:
Hono
↓
Drizzle ORM
↓
PostgreSQL
Drizzle provides the TypeScript interface used by the application to work with the database instead of writing every database operation manually.
One of the tables used while learning Drizzle was an author-style table.
Conceptually:
Author
-------------------------
id
name
birthday
createdAt
The ID is UUID-based.
birthday is optional, and createdAt gets a default timestamp from the database/ORM configuration.
A simplified schema looks like:
export const AuthorTable = pgTable('authors', {
id: uuid('id').defaultRandom().primaryKey(),
name: text('name').notNull(),
birthday: timestamp('birthday', {
withTimezone: true,
}),
createdAt: timestamp('created_at', {
withTimezone: true,
}).defaultNow(),
})The exact schema in the project may change as I continue learning.
The project uses Zod to validate incoming request data.
For example, a book creation schema can look like:
const createBookSchema = z.object({
title: z.string().min(1),
description: z.string().optional(),
publishDate: z.coerce.date().optional(),
pageCount: z.number().int().positive().optional(),
authorId: z.uuid(),
})The important idea is:
Incoming JSON
↓
Zod schema
↓
Valid?
/ \
yes no
| |
route error
This prevents the route from blindly trusting data coming from the client.
Authentication is another major learning goal of this project.
The important distinction to remember is:
"Who are you?"
"Are you allowed to do this?"
This project focuses primarily on understanding authentication and protecting API operations.
The authentication-related code is separated from the main application so that authentication logic does not need to be mixed into every route.
The API is organized around HTTP routes.
Typical REST operations follow this pattern:
| HTTP Method | Purpose |
|---|---|
GET |
Read data |
POST |
Create data |
PUT / PATCH |
Update data |
DELETE |
Delete data |
For example:
GET /authors
GET /authors/:id
POST /authors
PATCH /authors/:id
DELETE /authors/:id
The exact endpoints should be checked against the current source code because this project is still evolving.
A useful mental model for a request is:
HTTP Request
|
v
Hono Route
|
v
Authentication
|
v
Request Validation
|
v
Business Logic
|
v
Drizzle
|
v
PostgreSQL
|
v
JSON Response
If something goes wrong:
Request
|
+--> Authentication failure
|
+--> Validation failure
|
+--> Database failure
|
+--> Successful response
This is the overall backend architecture I want to remember from this project.
The project uses environment variables for configuration.
Create a .env file in the project root.
Example:
DATABASE_URL=your_postgres_connection_stringDo not commit the real .env file to GitHub.
Use .env.example if you want to document which variables are required:
DATABASE_URL=Then another developer can create their own .env.
git clone https://github.com/SwastidebDas/HonoAPIwithAuth.gitMove into the project:
cd HonoAPIwithAuthIf the application is inside my-app:
cd my-appUsing npm:
npm installThe project uses Docker for the local PostgreSQL database.
docker compose up -dCheck that the container is running:
docker psCreate:
.env
and add the required database configuration.
npm run devThe development setup uses tsx so TypeScript can be executed directly during development.
TypeScript can be checked/compiled with:
npm run buildor the corresponding TypeScript command configured in package.json.
The important distinction is:
tsx
|
+-- useful for development
+-- executes TypeScript
tsc
|
+-- TypeScript compiler
+-- type checking / compilation
There are two things that are easy to confuse:
Used by the application itself.
Example:
Application
↓
drizzle-orm
↓
PostgreSQL
Used for database development tasks such as generating and running migrations.
Think of it as a development/CLI tool around Drizzle.
Schema changes
↓
Drizzle Kit
↓
Migration
↓
PostgreSQL
The exact scripts are defined in package.json.
Typical commands used while developing this project are:
npm install
npm run dev
npm run buildFor Docker:
docker compose up -d
docker compose downFor Drizzle, use the scripts defined in package.json, for example:
npm run db:generate
npm run db:migrateif those scripts are configured.
This project was mainly useful for understanding the following concepts:
- Creating a Hono application
- Routes
- Route groups
app.route()- Request/response handling
- Middleware
- Hono
Context
- ESM imports/exports
- TypeScript configuration
tsctsx- Environment configuration
- Type safety
- Creating schemas
- Optional fields
- Type validation
z.coerce- UUID validation
- Validating request bodies
- Connecting an application to PostgreSQL
- Tables
- UUIDs
- Timestamps
- Basic queries
- Defining database schemas
- Querying tables
- ORM vs database
- Drizzle Kit
- Migrations
- Authentication vs authorization
- Protecting routes
- Authentication middleware
- Getting authenticated user information
This is important.
This project should not currently be considered production-ready.
It was created primarily for learning.
Things that would normally need more work before production include:
- Comprehensive error handling
- Proper logging
- Automated tests
- Better project-wide architecture
- Rate limiting
- Security hardening
- Input validation across every endpoint
- Production database configuration
- Database migration strategy
- Monitoring
- CI/CD
- API documentation
- Better authentication/authorization design
- Secret management
- Production deployment configuration
So the purpose of this repository is:
Learn first. Make it production-ready later.
When coming back to this project, these are the areas worth reviewing:
1. How Hono routing works
↓
2. How middleware works
↓
3. How authentication works
↓
4. How Zod validates requests
↓
5. How Hono passes validated data to handlers
↓
6. How Drizzle talks to PostgreSQL
↓
7. How migrations work
↓
8. How database errors are handled
↓
9. How authentication and authorization differ
↓
10. How to turn this into a production API
The most important thing to remember from this project is:
CLIENT
|
| HTTP
v
+-----------+
| Hono |
+-----------+
|
Middleware
|
+--------+--------+
| |
Authentication Other middleware
|
v
Routing
|
v
Validation
(Zod)
|
v
Route Handler
|
v
Drizzle ORM
|
v
PostgreSQL
Each layer has a different responsibility.
That separation is more important than the specific code in this small project.
If I come back to this project later, possible improvements are:
- Add proper centralized error handling
- Add authentication tests
- Add API integration tests
- Add more complete CRUD operations
- Improve folder structure
- Add authorization/roles
- Add pagination
- Add better database constraints
- Add API documentation
- Add request logging
- Add automated testing
- Add Docker configuration for the complete application
- Add CI using GitHub Actions
- Deploy the API
- Revisit the authentication implementation
- Make the project production-ready
- Hono Documentation
- Hono GitHub Repository
- Drizzle ORM Documentation
- Zod Documentation
- PostgreSQL Documentation
This repository is intentionally a learning project.
The code may not represent the architecture I would use for a large production application. The purpose is to keep a record of what I learned about building a TypeScript API with Hono, PostgreSQL, Drizzle, Zod, and authentication.
When I revisit this project later, I should focus less on remembering the exact syntax and more on understanding:
Request
↓
Middleware
↓
Authentication
↓
Validation
↓
Business Logic
↓
Database
↓
Response
That is the main thing this project was built to teach me.