Skip to content

Commit

Permalink
Update C3 OpenAPI template to allow users to pick either Hono or itty…
Browse files Browse the repository at this point in the history
…-router
  • Loading branch information
G4brym committed Jun 22, 2024
1 parent e8997b8 commit ec11cb1
Show file tree
Hide file tree
Showing 42 changed files with 1,370 additions and 382 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
---

fix: Update C3 OpenAPI template to allow users to pick either Hono or itty-router
41 changes: 40 additions & 1 deletion packages/create-cloudflare/templates/openapi/c3.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,48 @@
import { processArgument } from "@cloudflare/cli/args";
import type { C3Context } from "types";

const frameworkConfig = [
{
label: "Hono",
value: "hono",
},
{
label: "itty-router",
value: "itty-router",
},
];

export default {
configVersion: 1,
id: "openapi",
displayName: "API starter (OpenAPI compliant)",
platform: "workers",
copyFiles: {
path: "./ts",
async selectVariant(ctx: C3Context) {
const framework = await processArgument<string>(ctx.args, "framework", {
type: "select",
label: "framework",
question: "Which framework do you want to use?",
options: frameworkConfig,
defaultValue: frameworkConfig[0].value,
validate: (value) => {
if (
!frameworkConfig.map((obj) => obj.value).includes(String(value))
) {
return `Invalid framework \`${value}\`. Please choose one of the following: ${frameworkConfig.map((obj) => obj.value).join(", ")}.`;
}
},
});

return framework;
},
variants: {
hono: {
path: "./hono",
},
"itty-router": {
path: "./itty-router",
},
},
},
};
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.
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,18 @@
"version": "0.0.1",
"private": true,
"scripts": {
"cf-typegen": "wrangler types",
"deploy": "wrangler deploy",
"dev": "wrangler dev",
"start": "wrangler dev",
"cf-typegen": "wrangler types"
"start": "wrangler dev"
},
"dependencies": {
"@cloudflare/itty-router-openapi": "^1.0.1"
"chanfana": "^2.0.2",
"hono": "^4.4.7",
"zod": "^3.23.8"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20240605.0",
"@types/node": "20.8.3",
"@types/service-worker-mock": "^2.0.1",
"wrangler": "^3.60.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
@@ -0,0 +1,81 @@
import { Bool, OpenAPIRoute, Str } from "chanfana";
import { z } from "zod";
import { Task } from "../types";

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

async handle(c) {
// Get validated data
const data = await this.getValidatedData<typeof this.schema>();

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

// Implement your own object fetch here

const exists = true;

// @ts-ignore: check if the object exists
if (exists === false) {
return Response.json(
{
success: false,
error: "Object not found",
},
{
status: 404,
},
);
}

return {
success: true,
task: {
name: "my task",
slug: taskSlug,
description: "this needs to be done",
completed: false,
due_date: new Date().toISOString().slice(0, 10),
},
};
}
}
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
23 changes: 23 additions & 0 deletions packages/create-cloudflare/templates/openapi/hono/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
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";

// Start a Hono app
const app = new Hono();

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

// 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 the Hono app
export default app;
Loading

0 comments on commit ec11cb1

Please sign in to comment.