Skip to content

Commit

Permalink
Update c3 openapi template to use chanfana and Hono
Browse files Browse the repository at this point in the history
  • Loading branch information
G4brym committed Jun 20, 2024
1 parent d4e1e9f commit 34d5888
Show file tree
Hide file tree
Showing 23 changed files with 661 additions and 371 deletions.
6 changes: 6 additions & 0 deletions .changeset/metal-seahorses-occur.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"create-cloudflare": minor
"@cloudflare/prerelease-registry": patch
---

Update c3 openapi template to use chanfana and Hono
6 changes: 3 additions & 3 deletions packages/create-cloudflare/templates/openapi/ts/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Cloudflare Workers OpenAPI 3.1

This is a Cloudflare Worker with OpenAPI 3.1 using [itty-router-openapi](https://github.com/cloudflare/itty-router-openapi).
This is a Cloudflare Worker with OpenAPI 3.1 using [chanfana](https://github.com/cloudflare/chanfana) and [Hono](https://github.com/honojs/hono).

This is an example project made to be used as a quick start into building OpenAPI compliant Workers that generates the
`openapi.json` schema automatically from code and validates the incoming request to the defined parameters or request body.
Expand All @@ -16,10 +16,10 @@ This is an example project made to be used as a quick start into building OpenAP

1. Your main router is defined in `src/index.ts`.
2. Each endpoint has its own file in `src/endpoints/`.
3. For more information read the [itty-router-openapi official documentation](https://cloudflare.github.io/itty-router-openapi/).
3. For more information read the [chanfana documentation](https://chanfana.pages.dev/) and [Hono documentation](https://hono.dev/docs).

## Development

1. Run `wrangler dev` to start a local instance of the API.
2. Open `http://localhost:9000/` in your browser to see the Swagger interface where you can try the endpoints.
2. Open `http://localhost:8787/` in your browser to see the Swagger interface where you can try the endpoints.
3. Changes made in the `src/` folder will automatically trigger the server to reload, you only need to refresh the Swagger interface.
4 changes: 3 additions & 1 deletion packages/create-cloudflare/templates/openapi/ts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
"cf-typegen": "wrangler types"
},
"dependencies": {
"@cloudflare/itty-router-openapi": "^1.0.1"
"chanfana": "^2.0.2",
"zod": "^3.23.8",
"hono": "^4.4.7"
},
"devDependencies": {
"@types/node": "20.8.3",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,43 @@
import {
OpenAPIRoute,
OpenAPIRouteSchema,
} from "@cloudflare/itty-router-openapi";
import { Bool, OpenAPIRoute } from "chanfana";
import { z } from "zod";
import { Task } from "../types";

export class TaskCreate extends OpenAPIRoute {
static schema: OpenAPIRouteSchema = {
schema = {
tags: ["Tasks"],
summary: "Create a new Task",
requestBody: Task,
request: {
body: {
content: {
"application/json": {
schema: Task,
},
},
},
},
responses: {
"200": {
description: "Returns the created task",
schema: {
success: Boolean,
result: {
task: Task,
content: {
"application/json": {
schema: z.object({
series: z.object({
success: Bool(),
result: z.object({
task: Task,
}),
}),
}),
},
},
},
},
};

async handle(
request: Request,
env: any,
context: any,
data: Record<string, any>
) {
async handle(c) {
// Get validated data
const data = await this.getValidatedData<typeof this.schema>();

// Retrieve the validated request body
const taskToCreate = data.body;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,38 +1,39 @@
import {
OpenAPIRoute,
OpenAPIRouteSchema,
Path,
} from "@cloudflare/itty-router-openapi";
import { Bool, OpenAPIRoute, Str } from "chanfana";
import { z } from "zod";
import { Task } from "../types";

export class TaskDelete extends OpenAPIRoute {
static schema: OpenAPIRouteSchema = {
schema = {
tags: ["Tasks"],
summary: "Delete a Task",
parameters: {
taskSlug: Path(String, {
description: "Task slug",
request: {
params: z.object({
taskSlug: Str({ description: "Task slug" }),
}),
},
responses: {
"200": {
description: "Returns if the task was deleted successfully",
schema: {
success: Boolean,
result: {
task: Task,
content: {
"application/json": {
schema: z.object({
series: z.object({
success: Bool(),
result: z.object({
task: Task,
}),
}),
}),
},
},
},
},
};

async handle(
request: Request,
env: any,
context: any,
data: Record<string, any>
) {
async handle(c) {
// Get validated data
const data = await this.getValidatedData<typeof this.schema>();

// Retrieve the validated slug
const { taskSlug } = data.params;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,45 +1,52 @@
import {
OpenAPIRoute,
OpenAPIRouteSchema,
Path,
} from "@cloudflare/itty-router-openapi";
import { Bool, OpenAPIRoute, Str } from "chanfana";
import { z } from "zod";
import { Task } from "../types";

export class TaskFetch extends OpenAPIRoute {
static schema: OpenAPIRouteSchema = {
schema = {
tags: ["Tasks"],
summary: "Get a single Task by slug",
parameters: {
taskSlug: Path(String, {
description: "Task slug",
request: {
params: z.object({
taskSlug: Str({ description: "Task slug" }),
}),
},
responses: {
"200": {
description: "Returns a single task if found",
schema: {
success: Boolean,
result: {
task: Task,
content: {
"application/json": {
schema: z.object({
series: z.object({
success: Bool(),
result: z.object({
task: Task,
}),
}),
}),
},
},
},
"404": {
description: "Task not found",
schema: {
success: Boolean,
error: String,
content: {
"application/json": {
schema: z.object({
series: z.object({
success: Bool(),
error: Str(),
}),
}),
},
},
},
},
};

async handle(
request: Request,
env: any,
context: any,
data: Record<string, any>
) {
async handle(c) {
// Get validated data
const data = await this.getValidatedData<typeof this.schema>();

// Retrieve the validated slug
const { taskSlug } = data.params;

Expand All @@ -56,7 +63,7 @@ export class TaskFetch extends OpenAPIRoute {
},
{
status: 404,
}
},
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,43 +1,46 @@
import {
OpenAPIRoute,
OpenAPIRouteSchema,
Query,
} from "@cloudflare/itty-router-openapi";
import { Bool, Num, OpenAPIRoute } from "chanfana";
import { z } from "zod";
import { Task } from "../types";

export class TaskList extends OpenAPIRoute {
static schema: OpenAPIRouteSchema = {
schema = {
tags: ["Tasks"],
summary: "List Tasks",
parameters: {
page: Query(Number, {
description: "Page number",
default: 0,
}),
isCompleted: Query(Boolean, {
description: "Filter by completed flag",
required: false,
request: {
query: z.object({
page: Num({
description: "Page number",
default: 0,
}),
isCompleted: Bool({
description: "Filter by completed flag",
required: false,
}),
}),
},
responses: {
"200": {
description: "Returns a list of tasks",
schema: {
success: Boolean,
result: {
tasks: [Task],
content: {
"application/json": {
schema: z.object({
series: z.object({
success: Bool(),
result: z.object({
tasks: Task.array(),
}),
}),
}),
},
},
},
},
};

async handle(
request: Request,
env: any,
context: any,
data: Record<string, any>
) {
async handle(c) {
// Get validated data
const data = await this.getValidatedData<typeof this.schema>();

// Retrieve the validated parameters
const { page, isCompleted } = data.query;

Expand Down
34 changes: 14 additions & 20 deletions packages/create-cloudflare/templates/openapi/ts/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,23 @@
import { OpenAPIRouter } from "@cloudflare/itty-router-openapi";
import { fromHono } from "chanfana";
import { Hono } from "hono";
import { TaskCreate } from "./endpoints/taskCreate";
import { TaskDelete } from "./endpoints/taskDelete";
import { TaskFetch } from "./endpoints/taskFetch";
import { TaskList } from "./endpoints/taskList";

export const router = OpenAPIRouter({
// Start a Hono app
const app = new Hono();

// Setup OpenAPI registry
const openapi = fromHono(app, {
docs_url: "/",
});

router.get("/api/tasks/", TaskList);
router.post("/api/tasks/", TaskCreate);
router.get("/api/tasks/:taskSlug/", TaskFetch);
router.delete("/api/tasks/:taskSlug/", TaskDelete);

// 404 for everything else
router.all("*", () =>
Response.json(
{
success: false,
error: "Route not found",
},
{ status: 404 }
)
);
// Register OpenAPI endpoints
openapi.get("/api/tasks", TaskList);
openapi.post("/api/tasks", TaskCreate);
openapi.get("/api/tasks/:taskSlug", TaskFetch);
openapi.delete("/api/tasks/:taskSlug", TaskDelete);

export default {
fetch: router.handle,
} satisfies ExportedHandler;
// Export the Hono app
export default app;
17 changes: 9 additions & 8 deletions packages/create-cloudflare/templates/openapi/ts/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { DateTime, Str } from "@cloudflare/itty-router-openapi";
import { DateTime, Str } from "chanfana";
import { z } from "zod";

export const Task = {
name: new Str({ example: "lorem" }),
slug: String,
description: new Str({ required: false }),
completed: Boolean,
due_date: new DateTime(),
};
export const Task = z.object({
name: Str({ example: "lorem" }),
slug: Str(),
description: Str({ required: false }),
completed: z.boolean().default(false),
due_date: DateTime(),
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ export const repos = [
"workers-sdk",
"next-on-pages",
"pages-plugins",
"itty-router-openapi",
"chanfana",
];
Loading

0 comments on commit 34d5888

Please sign in to comment.