{"id":51168917,"url":"https://github.com/cjy1998/hono-rbac-starter","last_synced_at":"2026-06-26T22:30:56.556Z","repository":{"id":358277010,"uuid":"1237802872","full_name":"cjy1998/hono-rbac-starter","owner":"cjy1998","description":"A production-ready Hono + TypeScript backend starter with RBAC, JWT, Redis \u0026 Drizzle ORM | 开箱即用的 Hono 后端模板","archived":false,"fork":false,"pushed_at":"2026-06-11T14:26:58.000Z","size":165,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-06-11T16:05:33.037Z","etag":null,"topics":["drizzle-orm","honojs","ioredis","jwt","rbac","redis","rest-api","starter-template","typescript","winston","zod"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cjy1998.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-13T14:20:47.000Z","updated_at":"2026-06-11T14:31:13.000Z","dependencies_parsed_at":"2026-06-11T16:02:36.492Z","dependency_job_id":null,"html_url":"https://github.com/cjy1998/hono-rbac-starter","commit_stats":null,"previous_names":["cjy1998/hono-rbac-starter"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/cjy1998/hono-rbac-starter","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cjy1998%2Fhono-rbac-starter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cjy1998%2Fhono-rbac-starter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cjy1998%2Fhono-rbac-starter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cjy1998%2Fhono-rbac-starter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cjy1998","download_url":"https://codeload.github.com/cjy1998/hono-rbac-starter/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cjy1998%2Fhono-rbac-starter/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34835779,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-26T02:00:06.560Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["drizzle-orm","honojs","ioredis","jwt","rbac","redis","rest-api","starter-template","typescript","winston","zod"],"created_at":"2026-06-26T22:30:55.977Z","updated_at":"2026-06-26T22:30:56.543Z","avatar_url":"https://github.com/cjy1998.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# hono-rbac-starter\n\n[中文](./docs/README.zh-CN.md) | English\n\nA backend starter template based on **Hono + TypeScript**, featuring:\n\n- [Hono](https://hono.dev/) - A minimal, type-friendly Web framework\n- [Drizzle ORM](https://orm.drizzle.team/) + MySQL - A TypeScript-first ORM\n- [Zod](https://zod.dev/) - Schema validation and type inference\n- [Hono JWT](https://hono.dev/docs/helpers/jwt) - JWT signing and verification\n- [Argon2](https://github.com/ranisalt/node-argon2) - Modern password hashing algorithm\n- [Winston](https://github.com/winstonjs/winston) + [winston-daily-rotate-file](https://github.com/winstonjs/winston-daily-rotate-file) - Structured logging with daily rotation and automatic gzip compression\n- [ioredis](https://github.com/redis/ioredis) - Redis client used for role/permission caching\n- Security hardening middleware: security headers, login rate limiting (Redis sliding window), and basic XSS payload blocking\n- Layered architecture: `controller / server / dto / vo / db / middleware`\n- Unified exception handling, unified response format, and per-request `requestId` tracing\n- Complete RBAC schema: four entities (user / role / permission / menu) plus three association tables\n\n\u003e Want to read the full implementation walkthrough and module-by-module explanation? See [`TUTORIAL.md`](./TUTORIAL.md).\n\n---\n\n## Project Structure\n\n```\nhono-test/\n├── drizzle/                 Migration SQL generated by drizzle-kit\n├── logs/                    Runtime logs (gitignored, daily rotation + gzip compression)\n|── scripts/\n|   ├── seed.ts          Seed data initialization script\n├── src/\n│   ├── index.ts             Application entry (route mounting, global exceptions, request logging)\n│   ├── redis.ts             Redis connection instance (ioredis)\n│   ├── controller/           Controller layer\n│   ├── service/             Business layer\n│   │   ├── user.service.ts    User business logic\n│   │   └── permissions.ts    Permission/role query service (used by roleAuth)\n│   ├── db/                  Drizzle instance + schema\n│   │   ├── index.ts         Drizzle connection instance\n│   │   ├── schema/          Table definitions (one file per entity or relation)\n│   │   │   ├── common.ts      Shared columns for all tables (id / createdAt / updatedAt / deletedAt)\n│   │   │   ├── users.ts       Users table\n│   │   │   ├── roles.ts       Roles table\n│   │   │   ├── permissions.ts  Permissions table (button/API)\n│   │   │   ├── menus.ts       Menus table (separated from permissions)\n│   │   │   ├── user_role.ts    User-Role association table\n│   │   │   ├── role_permission.ts  Role-Permission association table\n│   │   │   └── role_menu.ts    Role-Menu association table\n│   │   └── relations.ts      Relation definitions among all tables\n│   ├── dto/                 Request DTOs + zod schemas\n│   │   ├── common.dto.ts     Common pagination DTO\n│   │   └── user.dto.ts       User DTOs\n│   ├── vo/                  Response view objects\n│   │   ├── common.vo.ts      Common pagination VO\n│   │   ├── roles.vo.ts       Roles VO\n│   │   └── user.vo.ts        User VO\n│   ├── middleware/          jwtAuth / roleAuth / redis.middleware / zValidator / requestLogger\n│   ├── exceptions/          Custom exceptions\n│   ├── types/               Shared types such as Hono Variables\n│   ├── utils/               Constants, unified response, logger, query utilities\n│   │   ├── const.ts          HTTP status constants\n│   │   ├── logger.ts         Winston logger\n│   │   ├── query.ts          Common query utilities (notDeleted, paginate)\n│   │   ├── response.ts       Unified response ok / fail\n│   │   └── token.ts          Bearer token parsing + JWT blacklist key helpers\n│   ├── env.ts               Runtime environment loading and validation\n│   └── env.d.ts             process.env types\n├── drizzle.config.ts\n├── tsconfig.json\n└── package.json\n```\n\n---\n\n## Database Design (RBAC)\n\nThis project implements a complete **RBAC (Role-Based Access Control)** schema consisting of 7 tables:\n\n```mermaid\nerDiagram\n    users {\n        string id PK \"UUID, primary key\"\n        string username UK \"Username, unique\"\n        string nickname \"Nickname\"\n        string password \"Argon2-hashed password\"\n        string email UK \"Email, unique\"\n        string phone \"Phone number\"\n        string avatar \"Avatar URL\"\n        tinyint status \"0-disabled 1-enabled\"\n        timestamp createdAt \"Created at\"\n        timestamp updatedAt \"Updated at\"\n        timestamp deletedAt \"Soft-delete time, nullable\"\n    }\n\n    roles {\n        string id PK \"UUID, primary key\"\n        string roleName \"Role name\"\n        string roleCode UK \"Role code, unique\"\n        int sortOrder \"Sort order, smaller comes first\"\n        tinyint status \"0-disabled 1-enabled\"\n        string remark \"Remark\"\n        timestamp createdAt \"Created at\"\n        timestamp updatedAt \"Updated at\"\n        timestamp deletedAt \"Soft-delete time\"\n    }\n\n    permissions {\n        string id PK \"UUID, primary key\"\n        string permissionName \"Permission name, e.g. 'User List'\"\n        string permissionCode UK \"Permission code, e.g. 'user:list', unique\"\n        tinyint permissionType \"1-button/action 2-API 3-data permission\"\n        string resource \"API resource path (used when permissionType=2)\"\n        string method \"HTTP method\"\n        tinyint status \"0-disabled 1-enabled\"\n        string remark \"Remark\"\n        timestamp createdAt \"Created at\"\n        timestamp updatedAt \"Updated at\"\n        timestamp deletedAt \"Soft-delete time\"\n    }\n\n    menus {\n        string id PK \"UUID, primary key\"\n        string parentId \"Parent menu ID, '0' for top level\"\n        string menuName \"Menu name\"\n        tinyint menuType \"1-directory 2-menu 3-external link\"\n        string path \"Route path\"\n        string component \"Frontend component path\"\n        string icon \"Icon\"\n        string redirect \"Redirect target\"\n        string permissionCode \"Associated permission code\"\n        tinyint visible \"0-hidden 1-visible\"\n        tinyint keepAlive \"0-no cache 1-cached\"\n        int sortOrder \"Sort order\"\n        tinyint status \"0-disabled 1-enabled\"\n        timestamp createdAt \"Created at\"\n        timestamp updatedAt \"Updated at\"\n        timestamp deletedAt \"Soft-delete time\"\n    }\n\n    user_role {\n        string userId PK,FK \"UUID referencing user\"\n        string roleId PK,FK \"UUID referencing role\"\n        timestamp createdAt \"Created at\"\n    }\n\n    role_permission {\n        string roleId PK,FK \"UUID referencing role\"\n        string permissionId PK,FK \"UUID referencing permission\"\n        timestamp createdAt \"Created at\"\n    }\n\n    role_menu {\n        string roleId PK,FK \"UUID referencing role\"\n        string menuId PK,FK \"UUID referencing menu\"\n        timestamp createdAt \"Created at\"\n    }\n\n    users ||--o{ user_role : \"A user can have multiple roles\"\n    roles ||--o{ user_role : \"A role can be assigned to multiple users\"\n    roles ||--o{ role_permission : \"A role can own multiple permissions\"\n    permissions ||--o{ role_permission : \"A permission can be assigned to multiple roles\"\n    roles ||--o{ role_menu : \"A role can be linked to multiple menus\"\n    menus ||--o{ role_menu : \"A menu can be assigned to multiple roles\"\n```\n\n**Design highlights**:\n\n- All primary keys use **UUID v4** (`char(36)`), which avoids conflicts during cross-environment migration\n- All tables include `createdAt / updatedAt / deletedAt` (soft delete), defined uniformly via `commonSchema`\n- **Menus are separated from permissions**: menus focus on frontend routing and components, while permissions focus on button actions and API authorization, decoupling the two concerns\n- The three association tables (user_role / role_permission / role_menu) have no `id` primary key and use composite primary keys (roleId + XxxId)\n\n---\n\n## Quick Start\n\n### 1. Requirements\n\n- Node.js \u003e= 20\n- MySQL \u003e= 8.0\n- Redis \u003e= 6.0\n- [pnpm](https://pnpm.io/) is recommended\n\n### 2. Install Dependencies\n\n```bash\npnpm install\n```\n\n### 3. Configure Environment Variables\n\nCreate a `.env` file in the project root:\n\n```dotenv\n# MySQL connection string\nDATABASE_URL=mysql://username:password@localhost:3306/hono_test\n\n# JWT secret and expiration\nJWT_SECRET=replace-with-your-own-long-random-string\nJWT_EXPIRES_IN=24h\n\n# Redis\nREDIS_HOST=127.0.0.1\nREDIS_PORT=6379\nREDIS_PASSWORD=\nREDIS_DB=0\n\n# Optional: logging\n# NODE_ENV=production      # In production the console output is also JSON\n# LOG_LEVEL=info           # error / warn / info / http / debug / silly\n# LOG_DIR=/var/log/hono    # Custom log directory, defaults to \u003ccwd\u003e/logs\n\n# Optional: CORS\nALLOWED_ORIGINS=*\n\n# Optional: login rate limiting\nRATE_LIMIT_LOGIN_MAX=5\nRATE_LIMIT_LOGIN_WINDOW_SEC=60\n```\n\nSee `.env.example` for more details.\n\n### 4. Create Database and Run Migrations\n\n```bash\n# Create the database in MySQL beforehand\nmysql -uroot -p -e \"CREATE DATABASE hono_test DEFAULT CHARSET utf8mb4;\"\n\n# Generate migration SQL from src/db/schema/\npnpm db:g\n\n# Apply migrations to the database\npnpm db:m\n\n# Initialize seed data (roles / permissions / menus / super admin account)\npnpm db:seed\n```\n\n### 5. Start the Dev Server\n\n```bash\npnpm dev\n```\n\nThe service runs at \u003chttp://localhost:3000\u003e by default.\n\n---\n\n## NPM Scripts\n\n| Command        | Description                                                            |\n| -------------- | ---------------------------------------------------------------------- |\n| `pnpm dev`     | `tsx watch src/index.ts`, dev mode with hot reload                     |\n| `pnpm build`   | `tsc`, compile TypeScript into `dist/`                                 |\n| `pnpm start`   | `node dist/index.js`, run the compiled artifact                        |\n| `pnpm db:g`    | `drizzle-kit generate`, generate migrations from schema                |\n| `pnpm db:m`    | `drizzle-kit migrate`, apply migrations to MySQL                       |\n| `pnpm db:seed` | `tsx scripts/seed.ts`, seed data (roles / permissions / menus / users) |\n\n---\n\n## Default Accounts\n\nAfter running `pnpm db:seed`, the following accounts are inserted automatically:\n\n| Role         | Username | Password      | Description                        |\n| ------------ | -------- | ------------- | ---------------------------------- |\n| Super Admin  | `admin`  | `admin123456` | Has all permissions and menus      |\n| Regular User | `user`   | `user123456`  | Can only access the Dashboard page |\n\n---\n\n## API Overview\n\n### User\n\n| Method | Path                 | Auth | Parameters                                       | Description                                           |\n| ------ | -------------------- | ---- | ------------------------------------------------ | ----------------------------------------------------- |\n| POST   | `/user/login`        | -    | `{ email, password }` (json)                     | Login, returns user + token (protected by rate limit) |\n| POST   | `/user/logout`       | JWT  | -                                                | Logout, adds current token to blacklist               |\n| POST   | `/user`              | JWT  | `{ username, nickname, email, password }` (json) | Create a user                                         |\n| GET    | `/user`              | JWT  | `?page\u0026pageSize\u0026username\u0026email` (query)          | Pagination + username/email search                    |\n| GET    | `/user/:id`          | JWT  | `id` (param, UUID format)                        | Get a single user                                     |\n| PUT    | `/user/:id`          | JWT  | `id` (param) + partial user fields (json)        | Update a user, also clears Redis cache                |\n| PUT    | `/user/:id/password` | JWT  | `id` (param) + `{ password }` (json)             | Change password, also clears Redis cache              |\n| DELETE | `/user/:id`          | JWT  | `id` (param, UUID format)                        | Soft-delete a user, also clears cache                 |\n| POST   | `/user/:id/roles`    | JWT  | `id` (param) + `{ roleIds: string[] }` (json)    | Bind user-role associations (idempotent batch)        |\n| DELETE | `/user/:id/roles`    | JWT  | `id` (param) + `{ roleIds: string[] }` (json)    | Unbind user-role associations (batch)                 |\n\n### Role\n\n| Method | Path                    | Auth | Parameters                                          | Description                                 |\n| ------ | ----------------------- | ---- | --------------------------------------------------- | ------------------------------------------- |\n| POST   | `/role`                 | JWT  | role payload (json)                                 | Create role                                 |\n| GET    | `/role`                 | JWT  | role filters + pagination (query)                   | List roles                                  |\n| GET    | `/role/:id`             | JWT  | `id` (param)                                        | Get role details                            |\n| PUT    | `/role/:id`             | JWT  | `id` (param) + role fields (json)                   | Update role                                 |\n| DELETE | `/role/:id`             | JWT  | `id` (param)                                        | Soft-delete role                            |\n| POST   | `/role/:id/permissions` | JWT  | `id` (param) + `{ permissionIds: string[] }` (json) | Bind role-permission associations (batch)   |\n| DELETE | `/role/:id/permissions` | JWT  | `id` (param) + `{ permissionIds: string[] }` (json) | Unbind role-permission associations (batch) |\n| POST   | `/role/:id/menus`       | JWT  | `id` (param) + `{ menuIds: string[] }` (json)       | Bind role-menu associations (batch)         |\n| DELETE | `/role/:id/menus`       | JWT  | `id` (param) + `{ menuIds: string[] }` (json)       | Unbind role-menu associations (batch)       |\n\n### Permission and Menu\n\n| Method | Path           | Auth | Description     |\n| ------ | -------------- | ---- | --------------- |\n| CRUD   | `/permissions` | JWT  | Permission CRUD |\n| CRUD   | `/menu`        | JWT  | Menu CRUD       |\n| GET    | `/menu/tree`   | JWT  | Query menu tree |\n\n\u003e All endpoints follow a unified response format:\n\u003e\n\u003e ```jsonc\n\u003e // Success\n\u003e { \"success\": true,  \"data\": { ... }, \"errorCode\": 0,   \"message\": \"ok\",          \"responseId\": \"\u003cuuid\u003e\" }\n\u003e\n\u003e // Failure\n\u003e { \"success\": false, \"data\": null,    \"errorCode\": 401, \"message\": \"token expired\", \"responseId\": \"\u003cuuid\u003e\" }\n\u003e ```\n\u003e\n\u003e `responseId` mirrors the request-scoped `x-request-id`, so frontend / gateway / log system can correlate a response with its full server-side trace.\n\n---\n\n## Integration Examples\n\n```bash\n# 1. Login\ncurl -X POST http://localhost:3000/user/login \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"email\":\"admin@example.com\",\"password\":\"admin123456\"}'\n\n# 2. Access protected endpoints with the token\ncurl http://localhost:3000/user \\\n  -H 'Authorization: Bearer \u003ctoken from step 1\u003e'\n\n# 3. Get a single user (id is a UUID)\ncurl http://localhost:3000/user/\u003cuuid\u003e \\\n  -H 'Authorization: Bearer \u003ctoken\u003e'\n\n# 4. Logout (token will be added to the Redis blacklist and become unusable)\ncurl -X POST http://localhost:3000/user/logout \\\n  -H 'Authorization: Bearer \u003ctoken\u003e'\n```\n\n---\n\n## Middleware\n\nThe project addresses cross-cutting concerns such as authentication, caching, and request tracing through Hono middleware, all located in `src/middleware/`:\n\n| Middleware         | Description                                                                                                                                                                                                                                                 |\n| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `jwtAuth`          | JWT parsing and verification; on success, writes the typed user payload into `c.set(\"user\", payload)` and the raw token into `c.set(\"token\", token)`. It also checks the Redis blacklist so logged-out tokens cannot be reused before they naturally expire |\n| `roleAuth`         | Role / permission authorization; based on `c.get(\"user\")`, calls the permissions service to check whether the current user has the required role or API permission                                                                                          |\n| `redis.middleware` | Role/permission caching middleware. On Redis cache hit it reuses the cached value; on miss it falls back to the database and writes the result back, reducing RBAC query load                                                                               |\n| `securityHeaders`  | Sets security-related response headers globally, including `CSP`, `X-Frame-Options`, and `X-Content-Type-Options`                                                                                                                                           |\n| `xssProtection`    | Performs basic XSS payload checks on request params/query/json body and rejects suspicious inputs                                                                                                                                                           |\n| `rateLimit`        | Redis sliding-window rate limit middleware, currently applied to `/user/login` to prevent brute-force attacks                                                                                                                                               |\n| `zValidator`       | Request parameter validation based on `@hono/zod-validator`; on failure throws a unified `ValidationException`                                                                                                                                              |\n| `requestLogger`    | Generates / propagates `x-request-id` and writes per-request trace logs                                                                                                                                                                                     |\n\n### Redis Cache\n\n- `src/redis.ts` creates a singleton connection based on `ioredis`, reading environment variables such as `REDIS_HOST / REDIS_PORT / REDIS_PASSWORD / REDIS_DB`\n- `redis.middleware` works together with `roleAuth` to cache the user's role and permission set, avoiding a database query on every request\n- Cache invalidation\n  - On user `update / delete / change password`, the controller calls `clearUserCache` to remove `user:{id}` and `user:roles:{id}` so the next read goes back to the database\n  - On logout, the current token is written into `jwt:blacklist:\u003csha256(token)\u003e` with a TTL equal to its remaining JWT lifetime (`exp - now`), so the key clears itself automatically and Redis never accumulates expired entries\n\n---\n\n## Logging System\n\nThe project ships with an engineering-grade logging solution based on Winston. All logs are written through the `logger` exposed by `src/utils/logger.ts`. **Do not use `console.log` in business code.**\n\n### Output Destinations\n\n| Channel                           | Content                       | Format                               |\n| --------------------------------- | ----------------------------- | ------------------------------------ |\n| Terminal console                  | Full output (per `LOG_LEVEL`) | Dev: colored single line; Prod: JSON |\n| `logs/application-YYYY-MM-DD.log` | Full output (per `LOG_LEVEL`) | JSON, easy for log collection        |\n| `logs/error-YYYY-MM-DD.log`       | Only `error` level            | JSON                                 |\n\n### Auto-Rotation Policy\n\n- Daily rotation: one file per day\n- Rolls immediately when a single file exceeds `20m`\n- Old files from previous days are automatically `gzip`-compressed into `.log.gz`\n- Full logs retained for `14d`, error logs retained for `30d`\n- Rotation state is recorded by `.\u003chash\u003e-audit.json`; expired files are deleted automatically\n\n### Environment Variables\n\n| Variable    | Default                         | Description                                                                 |\n| ----------- | ------------------------------- | --------------------------------------------------------------------------- |\n| `LOG_LEVEL` | `debug` in dev / `info` in prod | Standard winston level                                                      |\n| `LOG_DIR`   | `\u003ccwd\u003e/logs`                    | Custom log directory (in containers usually mounted on a persistent volume) |\n| `NODE_ENV`  | -                               | When set to `production`, the console also outputs JSON                     |\n\n### Request Tracing\n\nThe `requestLogger` middleware generates (or propagates from upstream) an `x-request-id` for each request and writes it into:\n\n- The response header `x-request-id`, allowing the frontend / gateway to correlate requests\n- The `requestId` field in all subsequent logs of the request, allowing a full trace to be filtered by ID in the log system\n\nTerminal example:\n\n```\n[2026-05-16 22:14:45.690] info: request received {\"requestId\":\"3c9d6ba5...\",\"method\":\"GET\",\"path\":\"/user/999\",...}\n[2026-05-16 22:14:45.691] warn: token is required {\"requestId\":\"3c9d6ba5...\",\"status\":401}\n[2026-05-16 22:14:45.691] info: request completed {\"requestId\":\"3c9d6ba5...\",\"method\":\"GET\",\"path\":\"/user/999\",\"status\":200,\"durationMs\":1}\n```\n\n### Usage in Business Code\n\n```ts\nimport { logger } from \"./utils/logger.js\";\n\nlogger.info(\"user created\", { userId, email });\nlogger.warn(\"rate limit hit\", { ip, route });\nlogger.error(\"payment failed\", { orderId, error: err });\n```\n\nWhen `logger.error` is called with an `Error` object directly, the stack trace is automatically expanded into the log.\n\n---\n\n## Further Reading\n\n- Full tutorial (module-by-module): [`TUTORIAL.md`](./TUTORIAL.md)\n- Hono official docs: \u003chttps://hono.dev/\u003e\n- Drizzle ORM docs: \u003chttps://orm.drizzle.team/\u003e\n- Zod docs: \u003chttps://zod.dev/\u003e\n\n---\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcjy1998%2Fhono-rbac-starter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcjy1998%2Fhono-rbac-starter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcjy1998%2Fhono-rbac-starter/lists"}