{"id":28894042,"url":"https://github.com/flowcore-io/hono-api","last_synced_at":"2026-03-09T22:31:28.820Z","repository":{"id":293060051,"uuid":"982820700","full_name":"flowcore-io/hono-api","owner":"flowcore-io","description":"Hono API Builder","archived":false,"fork":false,"pushed_at":"2025-07-24T08:58:55.000Z","size":181,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-10-11T01:36:19.200Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/flowcore-io.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"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}},"created_at":"2025-05-13T13:05:03.000Z","updated_at":"2025-07-24T08:58:23.000Z","dependencies_parsed_at":"2025-05-13T13:59:33.756Z","dependency_job_id":"0bafc23b-48b7-4099-9b1a-113f40f1a9a8","html_url":"https://github.com/flowcore-io/hono-api","commit_stats":null,"previous_names":["flowcore-io/hono-api"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/flowcore-io/hono-api","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flowcore-io%2Fhono-api","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flowcore-io%2Fhono-api/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flowcore-io%2Fhono-api/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flowcore-io%2Fhono-api/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/flowcore-io","download_url":"https://codeload.github.com/flowcore-io/hono-api/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flowcore-io%2Fhono-api/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30314627,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-09T20:05:46.299Z","status":"ssl_error","status_checked_at":"2026-03-09T19:57:04.425Z","response_time":61,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":[],"created_at":"2025-06-21T03:42:35.765Z","updated_at":"2026-03-09T22:31:28.801Z","avatar_url":"https://github.com/flowcore-io.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Flowcore Hono API Builder\n\n## OpenTelemetry Trace-Log Correlation\n\nFor trace-log correlation to work properly, **initialization order is critical**:\n\n### ✅ Correct Setup\n\n```ts\n// main.ts - FIRST THING IN APPLICATION\nimport init from \"@flowcore/hono-api/otel\"\n\n// Initialize OTEL before any other imports\ninit({\n  otelServiceName: \"my-api\",\n  otelEndpoint: \"http://localhost:4318\", // OTLP HTTP endpoint\n  runtime: \"bun\" // or \"node\" | \"deno\"\n})\n\n// Now safe to import and use the library\nimport { HonoApi, HonoApiRouter, loggerFactory } from \"@flowcore/hono-api\"\nimport { z } from \"@hono/zod-openapi\"\n\n// Create logger with trace correlation\nconst createLogger = loggerFactory({\n  prettyPrintLogs: true,\n  logLevel: \"info\"\n}).createLogger\n\nconst logger = createLogger(\"main\")\n\n// Your logs will now include trace_id and span_id automatically\nlogger.info(\"Application starting\", { port: 3000 })\n```\n\n### ❌ Common Mistakes\n\n```ts\n// WRONG: Creating logger before OTEL initialization\nimport { loggerFactory } from \"@flowcore/hono-api\"\nconst logger = loggerFactory(...).createLogger(\"main\") // No trace correlation!\n\nimport init from \"@flowcore/hono-api/otel\"\ninit({ ... }) // Too late - logger already created\n```\n\n### Environment Variables\n\n| Variable | Type | Description | Default | Required |\n|----------|------|-------------|----------|----------|\n| OTEL_SERVICE_NAME | string | Service name for traces | - | ✓ |\n| OTEL_EXPORTER_OTLP_ENDPOINT | string | OTLP endpoint URL | - | ✓ |\n\n## Global JWT Configuration\n\nHonoApi supports **global JWT configuration** for custom OIDC providers like Keycloak, Okta, or Auth0. This eliminates the need to configure JWT validation per route.\n\n### ✅ Global JWT Configuration\n\n```ts\nimport { HonoApi, JWTValidationConfig, FLOWCORE_JWT_CONFIG } from \"@flowcore/hono-api\"\nimport { z } from \"@hono/zod-openapi\"\n\n// Custom JWT configuration for Keycloak\nconst keycloakConfig: JWTValidationConfig = {\n  extractUserId: (payload) =\u003e payload.sub as string,\n  extractEmail: (payload) =\u003e payload.email as string,\n  extractIsAdmin: (payload) =\u003e {\n    const roles = payload.realm_access?.roles || []\n    return roles.includes(\"admin\")\n  },\n  validatePayload: (payload) =\u003e {\n    if (!payload.sub) {\n      throw new Error(\"Missing subject in JWT\")\n    }\n    if (!payload.email_verified) {\n      throw new Error(\"Email not verified\")\n    }\n  }\n}\n\n// Option 1: Global config via auth.jwtConfig\nconst api = new HonoApi({\n  auth: {\n    jwks_url: \"https://your-keycloak.com/realms/your-realm/protocol/openid-connect/certs\",\n    jwtConfig: keycloakConfig\n  }\n})\n\n// Option 2: Global config via authDefaults (takes precedence)\nconst api = new HonoApi({\n  auth: {\n    jwks_url: \"https://your-keycloak.com/realms/your-realm/protocol/openid-connect/certs\",\n  },\n  authDefaults: {\n    jwtConfig: keycloakConfig,\n    optional: true // Apply to routes without explicit auth config\n  }\n})\n```\n\n### **JWT Configuration Interface**\n\n```ts\ninterface JWTValidationConfig {\n  extractUserId: (payload: JWTPayload) =\u003e string\n  extractEmail?: (payload: JWTPayload) =\u003e string | undefined  \n  extractIsAdmin?: (payload: JWTPayload) =\u003e boolean\n  validatePayload?: (payload: JWTPayload) =\u003e void\n}\n```\n\n### **OIDC Provider Examples**\n\n**Keycloak Configuration:**\n```ts\nconst keycloakConfig: JWTValidationConfig = {\n  extractUserId: (payload) =\u003e payload.sub as string,\n  extractEmail: (payload) =\u003e payload.email as string,\n  extractIsAdmin: (payload) =\u003e {\n    const realmRoles = payload.realm_access?.roles || []\n    return realmRoles.includes(\"admin\")\n  }\n}\n```\n\n**Auth0 Configuration:**\n```ts\nconst auth0Config: JWTValidationConfig = {\n  extractUserId: (payload) =\u003e payload.sub as string,\n  extractEmail: (payload) =\u003e payload.email as string,\n  extractIsAdmin: (payload) =\u003e {\n    const roles = payload[\"https://myapp.com/roles\"] || []\n    return roles.includes(\"admin\")\n  }\n}\n```\n\n**Okta Configuration:**\n```ts\nconst oktaConfig: JWTValidationConfig = {\n  extractUserId: (payload) =\u003e payload.uid as string,\n  extractEmail: (payload) =\u003e payload.email as string,\n  extractIsAdmin: (payload) =\u003e {\n    const groups = payload.groups || []\n    return groups.includes(\"Administrators\")\n  }\n}\n```\n\n### **Benefits**\n\n- ✅ **No per-route configuration** - JWT config applies globally\n- ✅ **OIDC provider flexibility** - Support any JWT issuer  \n- ✅ **Backward compatibility** - Flowcore auth still works by default\n- ✅ **Custom validation** - Add business logic to JWT validation\n- ✅ **Role-based access** - Extract admin/user roles from JWT claims\n\n## Usage\n\n```ts\nimport { HonoApi, HonoApiRouter } from \"../src/mod.ts\"\nimport { z } from \"@hono/zod-openapi\"\n\n// root-router.ts\nconst rootRouter = new HonoApiRouter()\nrootRouter.get(\"/health\", {\n  tags: [\"root\"],\n  auth: {\n    optional: true,\n  },\n  output: z.object({\n    status: z.enum([\"ok\", \"error\"]).openapi({\n      example: \"ok\",\n    }),\n  }),\n  handler: () =\u003e {\n    return {\n      status: \"ok\" as const,\n    }\n  },\n})\n\n// data-cores-router.ts\nconst apiV1DataCoreRouter = new HonoApiRouter()\napiV1DataCoreRouter.post(\"/\", {\n  summary: \"Create a data core\",\n  description: \"Create a data core\",\n  tags: [\"data-core\"],\n  auth: {\n    permissions: (input) =\u003e {\n      return [{\n        action: \"write\",\n        resource: [`frn::${input.body.tenantId}:data-core:*`],\n      }]\n    },\n  },\n  input: {\n    body: z.object({\n      name: z.string().openapi({\n        example: \"my-data-core\",\n      }),\n      tenantId: z.string().openapi({\n        example: \"00000000-0000-0000-0000-000000000000\",\n        description: \"The ID of the tenant to create the data core on\",\n      }),\n    }),\n  },\n  output: z.object({\n    id: z.string(),\n    name: z.string(),\n    tenantId: z.string(),\n  }),\n  handler: (input) =\u003e {\n    return {\n      id: crypto.randomUUID(),\n      name: input.body.name,\n      tenantId: input.body.tenantId,\n    }\n  },\n})\n\n// Hono API\nconst honoApi = new HonoApi({\n  // Auth is optional (These are the defaults)\n  auth: {\n    jwks_url: \"https://auth.flowcore.io/realms/flowcore/protocol/openid-connect/certs\",\n    api_key_url: \"https://iam.api.flowcore.io\",\n    iam_url: \"https://iam.api.flowcore.io\",\n  },\n  // OpenAPI is optional (These are the defaults)\n  openapi: {\n    docPath: \"/swagger\",\n    jsonPath: \"/swagger/openapi.json\",\n    version: \"0.0.1\",\n    name: \"Hono API\",\n    description: \"Hono API\",\n  },\n  // Logger is optional (This is the default)\n  logger: console,\n})\n\nhonoApi.addRouter(\"/\", rootRouter)\nhonoApi.addRouter(\"/api/v1/data-cores\", apiV1DataCoreRouter)\n\n// Serve your api in your preferred manner\nDeno.serve({ port: 3000 }, honoApi.app.fetch)\n\nBun.serve({\n  port: 3000,\n  fetch: honoApi.app.fetch\n})\n```\n\n## Testing\n\nThe project includes comprehensive tests for the API functionality:\n\n```bash\n# Run all tests\ndeno task test\n\n# Run tests in watch mode\ndeno task test:watch\n\n# Run tests with coverage\ndeno task test:coverage\n\n# Type check all files\ndeno task typecheck\n```\n\n### Test Coverage\n\nThe test suite covers:\n\n- **HonoApi Class**: Initialization, configuration, router mounting, request handling\n- **HonoApiRouter Class**: Route registration, path handling, HTTP methods, configuration\n- **Exception Classes**: All error types with proper status codes and serialization\n- **Request/Response Handling**: GET, POST, PUT, PATCH, DELETE methods\n- **Input Validation**: Headers, query parameters, path parameters, request bodies\n- **OpenAPI Documentation**: Spec generation and documentation serving\n- **Prometheus Metrics**: Metrics collection and endpoint protection\n- **Error Handling**: Internal server errors and custom exceptions\n\nAuthentication tests are excluded as requested, but can be added separately if needed.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflowcore-io%2Fhono-api","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fflowcore-io%2Fhono-api","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflowcore-io%2Fhono-api/lists"}