{"id":15723455,"url":"https://github.com/oultimocoder/cloudflare-planetscale-hono-boilerplate","last_synced_at":"2025-04-06T12:10:30.209Z","repository":{"id":65142772,"uuid":"562268063","full_name":"OultimoCoder/cloudflare-planetscale-hono-boilerplate","owner":"OultimoCoder","description":"A boilerplate for building production-ready RESTful APIs using Cloudflare, Hono, and Planetscale","archived":false,"fork":false,"pushed_at":"2024-09-10T13:09:36.000Z","size":948,"stargazers_count":209,"open_issues_count":2,"forks_count":23,"subscribers_count":5,"default_branch":"main","last_synced_at":"2025-03-30T10:08:35.400Z","etag":null,"topics":["api","boilerplate","cloudflare","cloudflare-workers","durable-objects","email-sender","hono","jwt","oauth","oauth2","planetscale","rate-limiter","rate-limiting","rest-api","starter","typescript"],"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/OultimoCoder.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}},"created_at":"2022-11-05T20:11:22.000Z","updated_at":"2025-03-18T18:33:02.000Z","dependencies_parsed_at":"2024-08-24T15:44:26.276Z","dependency_job_id":"937650d1-c8e2-4862-a9cc-4377bd8b0303","html_url":"https://github.com/OultimoCoder/cloudflare-planetscale-hono-boilerplate","commit_stats":null,"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/OultimoCoder%2Fcloudflare-planetscale-hono-boilerplate","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/OultimoCoder%2Fcloudflare-planetscale-hono-boilerplate/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/OultimoCoder%2Fcloudflare-planetscale-hono-boilerplate/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/OultimoCoder%2Fcloudflare-planetscale-hono-boilerplate/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/OultimoCoder","download_url":"https://codeload.github.com/OultimoCoder/cloudflare-planetscale-hono-boilerplate/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247478324,"owners_count":20945266,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["api","boilerplate","cloudflare","cloudflare-workers","durable-objects","email-sender","hono","jwt","oauth","oauth2","planetscale","rate-limiter","rate-limiting","rest-api","starter","typescript"],"created_at":"2024-10-03T22:11:43.328Z","updated_at":"2025-04-06T12:10:30.185Z","avatar_url":"https://github.com/OultimoCoder.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# RESTful API Cloudflare Workers Boilerplate\nA boilerplate/starter project for quickly building RESTful APIs using\n[Cloudflare Workers](https://workers.cloudflare.com/), [Hono](https://honojs.dev/), and\n[PlanetScale](https://planetscale.com/). Inspired by\n[node-express-boilerplate](https://github.com/hagopj13/node-express-boilerplate) by hagopj13.\n\n## Quick Start\n\nTo create a project, simply run:\n\n```bash\nnpx create-cf-planetscale-app \u003cproject-name\u003e\n```\n\nOr\n\n```bash\nnpm init cf-planetscale-app \u003cproject-name\u003e\n```\n\n## Table of Contents\n\n- [RESTful API Cloudflare Workers Boilerplate](#restful-api-cloudflare-workers-boilerplate)\n  - [Quick Start](#quick-start)\n  - [Table of Contents](#table-of-contents)\n  - [Features](#features)\n  - [Commands](#commands)\n  - [Error Handling](#error-handling)\n  - [Validation](#validation)\n  - [Authentication](#authentication)\n  - [Emails](#emails)\n  - [Authorisation](#authorisation)\n  - [Rate Limiting](#rate-limiting)\n  - [Contributing](#contributing)\n  - [Inspirations](#inspirations)\n  - [License](#license)\n\n## Features\n\n- **SQL database**: [PlanetScale](https://planetscale.com/) using\n[Kysely](https://github.com/koskimas/kysely) as a type-safe SQl query builder\n- **Authentication and authorization**: using JWT\n- **Validation**: request data validation using [Zod](https://github.com/colinhacks/zod)\n- **Logging**: using [Sentry](https://sentry.io/)\n- **Testing**: unit and integration tests using [Vitest](https://vitest.dev/)\n- **Error handling**: centralised error handling mechanism provided by [Hono](https://honojs.dev/)\n- **Git hooks**: with [Husky](https://github.com/typicode/husky)\n- **Linting**: with [ESLint](https://eslint.org) and [Prettier](https://prettier.io)\n- **Emails**: with [Amazon SES](https://aws.amazon.com/ses/)\n- **Oauth**: Support for Discord, Github, Spotify, Google, Apple and Facebook. Support coming for\n  Instagram and Twitter\n- **Rate Limiting**: using Cloudflare durable objects you can rate limit endpoints usin the sliding\n  window algorithm\n\n## Commands\n\nRunning locally:\n\n```bash\nnpm run dev\n```\n\nTesting:\n\n```bash\n# run all tests\nnpm run tests\n\n# run test coverage\nnpm run tests:coverage\n```\n\nLinting:\n\n```bash\n# run ESLint\nnpm run lint\n\n# fix ESLint errors\nnpm run lint:fix\n\n# run prettier\nnpm run prettier\n\n# fix prettier errors\nnpm run prettier:fix\n```\n\nMigrations:\n\nTo deploy to production you must first deploy to a test/dev branch on Planetscale and then create\na deploy request and merge the schema into production.\n\n```bash\n# run all migrations for testing\nnpm run migrate:test:latest\n\n# remove all migrations for testing\nnpm run migrate:test:none\n\n# revert last migration for testing\nnpm run migrate:test:down\n```\n\nDeploy to Cloudflare:\n\n```bash\nnpm run deploy\nnpm run deploy\n```\n\n## Error Handling\n\nThe app has a centralized error handling mechanism provided by [Hono](https://honojs.dev/).\n\n```javascript\napp.onError(errorHandler)\n```\n\nAll errors will be caught by the errorHandler which converts the error to an ApiError and formats\nit in a JSON response. Any errors that aren't intentionally thrown, e.g. 500 errors, are logged to\nSentry.\n\nThe error handling middleware sends an error response, which has the following format:\n\n```json\n{\n  \"code\": 404,\n  \"message\": \"Not found\"\n}\n```\n\nWhen running in development mode, the error response also contains the error stack.\n\n## Validation\n\nRequest data is validated using [Zod](https://github.com/colinhacks/zod).\n\nThe validation schemas are defined in the `src/validations` directory and are used in the\ncontrollers by getting either the query or body and then calling the parse on the relevant\nvalidation function:\n\n\n```javascript\nconst getUsers: Handler\u003c{ Bindings: Bindings }\u003e = async (c) =\u003e {\n  const config = getConfig(c.env)\n  const queryParse = c.req.query()\n  const query = userValidation.getUsers.parse(queryParse)\n  const filter = { email: query.email }\n  const options = { sortBy: query.sort_by, limit: query.limit, page: query.page }\n  const result = await userService.queryUsers(filter, options, config.database)\n  return c.json(result, httpStatus.OK)\n}\n```\n\n## Authentication\n\nTo require authentication for certain routes, you can use the `auth` middleware.\n\n```javascript\nimport { Hono } from 'hono'\nimport * as userController from '../controllers/user.controller'\nimport { auth } from '../middlewares/auth'\n\nconst route = new Hono\u003c{ Bindings: Bindings }\u003e()\n\nroute.post('/', auth(), userController.createUser)\n\nexport { route }\n\n```\n\nThese routes require a valid JWT access token in the Authorization request header using the Bearer\nschema. If the request does not contain a valid access token, an Unauthorized (401) error is thrown.\n\n## Emails\n\nSupport for Email sending using [Amazon SES](https://aws.amazon.com/ses/). Just call the `sendEmail`\nfunction in `src/services/email.service.ts`:\n\n```javascript\nconst sendResetPasswordEmail = async (email: string, emailData: EmailData, config: Config) =\u003e {\n  const message = {\n    Subject: {\n      Data: 'Reset your password',\n      Charset: 'UTF-8'\n    },\n    Body: {\n      Text: {\n        Charset: 'UTF-8',\n        Data: `\n          Hello ${emailData.name}\n          Please reset your password by clicking the following link:\n          ${emailData.token}\n        `\n      }\n    }\n  }\n  await sendEmail(email, config.email.sender, message, config.aws)\n}\n```\n\n## Authorisation\n\nThe `auth` middleware can also be used to require certain rights/permissions to access a route.\n\n```javascript\nimport { Hono } from 'hono'\nimport * as userController from '../controllers/user.controller'\nimport { auth } from '../middlewares/auth'\n\nconst route = new Hono\u003c{ Bindings: Bindings }\u003e()\n\nroute.post('/', auth('manageUsers'), userController.createUser)\n\nexport { route }\n```\n\nIn the example above, an authenticated user can access this route only if that user has the\n`manageUsers` permission.\n\nThe permissions are role-based. You can view the permissions/rights of each role in the\n`src/config/roles.ts` file.\n\nIf the user making the request does not have the required permissions to access this route, a\nForbidden (403) error is thrown.\n\n## Rate Limiting\n\nTo apply rate limits for certain routes, you can use the `rateLimit` middleware.\n\n```javascript\nimport { Hono } from 'hono'\nimport { Environment } from '../../bindings'\nimport { auth } from '../middlewares/auth'\nimport { rateLimit } from '../middlewares/rateLimiter'\n\nexport const route = new Hono\u003cEnvironment\u003e()\n\nconst twoMinutes = 120\nconst oneRequest = 1\n\nroute.post(\n  '/send-verification-email',\n  auth(),\n  rateLimit(twoMinutes, oneRequest),\n  authController.sendVerificationEmail\n)\n```\n\nThis uses Cloudflare durable objects to apply rate limits using the sliding window algorithm. You\ncan specify the interval size in seconds and how many requests are allowed per interval.\n\nIf the rate limit is hit a `429` will be returned to the client.\n\nThese headers are returned with each endpoint that has rate limiting applied:\n\n* `X-RateLimit-Limit` - How many requests are allowed per window\n* `X-RateLimit-Reset` - How many seconds until the current window resets\n* `X-RateLimit-Policy` - Details about the rate limit policy in this format `${limit};w=${interval};comment=\"Sliding window\"`\n* `X-RateLimit-Remaining` - How many requests you can send until you will be rate limited. Please\nnote this doesn't just reset to the limit when the reset period hits. Use it as indicator of your\ncurrent throughput e.g. if you have 12 requests allowed every 1 second and remaining is 0\nyou are at 100% throughput, but if it is 6 you are 50% throughput. This value constantly changes\nas the window progresses either increasing or decreasing based on your throughput\n\nThe rate limit will be based on IP unless the user is authenticated then it will be based on the\nuser ID.\n\n## Contributing\n\nContributions are more than welcome!\n\n## Inspirations\n\n- [hagopj13/node-express-boilerplate](https://github.com/hagopj13/node-express-boilerplate)\n\n## License\n\n[MIT](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foultimocoder%2Fcloudflare-planetscale-hono-boilerplate","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Foultimocoder%2Fcloudflare-planetscale-hono-boilerplate","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foultimocoder%2Fcloudflare-planetscale-hono-boilerplate/lists"}