Skip to content

Commit

Permalink
feat: Add CRUD example; Parse and validate request.body (#8)
Browse files Browse the repository at this point in the history
  • Loading branch information
drewdecarme authored Nov 2, 2023
1 parent 607bea5 commit 4e5a38f
Show file tree
Hide file tree
Showing 38 changed files with 662 additions and 31 deletions.
5 changes: 5 additions & 0 deletions .changeset/hot-pots-divide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@flare-city/core": minor
---

Adds another example & template that details CRUD setup; Allows parsing of request.body
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
8 changes: 8 additions & 0 deletions packages/cli/examples/with-crud/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const path = require("path");

module.exports = {
extends: "@flare-city/eslint-config",
parserOptions: {
project: path.resolve(__dirname, "./tsconfig.json"),
},
};
4 changes: 4 additions & 0 deletions packages/cli/examples/with-crud/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules
# Keep environment variables out of version control
.env
.wrangler
8 changes: 8 additions & 0 deletions packages/cli/examples/with-crud/eslint.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const path = require("path");

module.exports = {
extends: "@flare-city/eslint-config",
parserOptions: {
project: path.resolve(__dirname, "./tsconfig.json"),
},
};
34 changes: 34 additions & 0 deletions packages/cli/examples/with-crud/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"name": "with-crud",
"version": "0.0.0",
"private": true,
"description": "with-crud",
"main": "dist/index.js",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"type": "module",
"license": "MIT",
"scripts": {
"deploy": "wrangler deploy",
"dev": "wrangler dev --port=8282",
"build": "yarn tsc --project tsconfig.types.json",
"test": "yarn flare-city test",
"lint": "npx eslint . --fix"
},
"dependencies": {
"@flare-city/core": "workspace:*",
"@flare-city/logger": "workspace:*",
"zod": "3.22.4"
},
"devDependencies": {
"@cloudflare/workers-types": "latest",
"@flare-city/cli": "workspace:*",
"@flare-city/eslint-config": "workspace:*",
"@flare-city/test": "workspace:*",
"@flare-city/tsconfig": "workspace:*",
"@types/node": "20.8.9",
"dotenv-cli": "7.3.0",
"typescript": "5.2.2",
"wrangler": "3.15.0"
}
}
1 change: 1 addition & 0 deletions packages/cli/examples/with-crud/src/features/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./post";
4 changes: 4 additions & 0 deletions packages/cli/examples/with-crud/src/features/post/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from "./post.route";

// GET
export * from "./post.crud";
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { WorkerTest } from "@flare-city/test";
import { expect, test, describe } from "vitest";
import type { GetAllPostsApiResponse } from "./post.crud";

describe("GET /post", () => {
let worker: WorkerTest;

test("Should response with 2 entries", async () => {
worker = new WorkerTest(global.worker);
const res = await worker.get<GetAllPostsApiResponse>({
endpoint: "/post",
});
expect(res.json.data).toHaveLength(2);
});
});
164 changes: 164 additions & 0 deletions packages/cli/examples/with-crud/src/features/post/post.crud.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import type { ApiResponse, RequestURLSegments } from "@flare-city/core";
import { RoutePost } from "./post.route";
import { z } from "zod";

// Post Schema
const PostSchema = z.object({
title: z.string(),
description: z.string(),
});

// Segments
const PostApiSegmentsSchema = z.object({ id: z.string() });
export type PostApiSegments = z.infer<typeof PostApiSegmentsSchema>;

// GET Response & Params
const GetSinglePostApiResponseSchema = PostSchema.extend({ id: z.string() });
export type GetSinglePostApiResponse = ApiResponse<
z.infer<typeof GetSinglePostApiResponseSchema>
>;

// GET All Response & Params
export type GetAllPostsApiResponse = ApiResponse<
z.infer<typeof GetSinglePostApiResponseSchema>[]
>;
const GetAllPostsApiSearchParamsSchema = z.object({
search: z.string().optional(),
amount: z.coerce.number().optional(),
type: z.union([z.literal("test-1"), z.literal("test-2")]).optional(),
});
export type GetAllPostsApiSearchParams = z.infer<
typeof GetAllPostsApiSearchParamsSchema
>;

// POST & PUT Request
const CreateSinglePostApiRequestSchema = PostSchema;
export type CreateSinglePostApiRequest = z.infer<
typeof CreateSinglePostApiRequestSchema
>;
export type CreateSinglePostApiResponse = GetSinglePostApiResponse;

/**
* Get's all of the posts
* @description GET /post
*/
RoutePost.get<
GetAllPostsApiResponse,
RequestURLSegments,
GetAllPostsApiSearchParams
>({
path: "",
parse: {
params: GetAllPostsApiSearchParamsSchema,
},
handler: async (req, env, context, res) => {
return res({
json: {
data: [
{
id: "1",
title: "post title",
description: "post description",
},
{
id: "2",
title: "post title",
description: "post description",
},
],
},
status: 200,
});
},
});

/**
* Get's a single post by ID
* @description GET /post/:id
*/
RoutePost.get<GetSinglePostApiResponse, PostApiSegments>({
path: "/:id",
parse: {
segments: PostApiSegmentsSchema,
},
handler: async (req, env, context, res) => {
return res({
json: {
data: {
id: context.segments.id,
title: "post title",
description: "post description",
},
},
status: 200,
});
},
});

/**
* Creates a new post
* @description POST /post
*/
RoutePost.post<CreateSinglePostApiResponse, CreateSinglePostApiRequest>({
path: "",
parse: {
body: CreateSinglePostApiRequestSchema,
},
handler: async (req, env, context, res) => {
return res({
json: {
data: {
id: "1",
description: context.body.description,
title: context.body.title,
},
},
status: 200,
});
},
});

/**
* Updates a post with the provided :id segment
* @description PUT /post/:id
*/
RoutePost.put<CreateSinglePostApiResponse, CreateSinglePostApiRequest>({
path: "/:id",
parse: {
segments: PostApiSegmentsSchema,
body: CreateSinglePostApiRequestSchema,
},
handler: async (req, env, context, res) => {
return res({
json: {
data: {
id: context.segments.id,
title: context.body.title,
description: context.body.description,
},
},
status: 200,
});
},
});

/**
* Deletes a post with the provided :id segment
* @description DELETE /post/:id
*/
RoutePost.delete({
path: "/:id",
parse: {
segments: PostApiSegmentsSchema,
},
handler: async (req, env, context, res) => {
return res({
json: {
data: {
message: "Running the DELETE handler",
},
},
status: 200,
});
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { Route } from "@flare-city/core";

export const RoutePost = new Route({ root: "/post" });
11 changes: 11 additions & 0 deletions packages/cli/examples/with-crud/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { App } from "@flare-city/core";
import { RoutePost } from "./features";

// Declare a new application
export const API = new App("with-crud");

// Add routes
API.addRoute(RoutePost);

// Start the API
export default API.start();
1 change: 1 addition & 0 deletions packages/cli/examples/with-crud/src/lib/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./logger";
6 changes: 6 additions & 0 deletions packages/cli/examples/with-crud/src/lib/logger/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { WorkersLogger } from "@flare-city/logger";

export const log = new WorkersLogger({
name: "with-crud",
level: "debug",
});
15 changes: 15 additions & 0 deletions packages/cli/examples/with-crud/src/types/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type {
RequestURLSearchParams,
RequestURLSegments,
} from "@flare-city/core";

declare global {
interface ExecutionContext<
T extends RequestURLSegments = RequestURLSegments,
P extends RequestURLSearchParams = RequestURLSearchParams,
> {
segments: T;
params: P;
sample: string;
}
}
7 changes: 7 additions & 0 deletions packages/cli/examples/with-crud/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "@flare-city/tsconfig/worker",
"compilerOptions": {
"types": ["@flare-city/test/types"]
},
"include": ["./src/**/*.ts"]
}
9 changes: 9 additions & 0 deletions packages/cli/examples/with-crud/tsconfig.types.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"noEmit": false,
"emitDeclarationOnly": true,
"declaration": true,
"declarationDir": "./dist"
}
}
7 changes: 7 additions & 0 deletions packages/cli/examples/with-crud/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { defineConfig } from "vitest/config";

export default defineConfig({
test: {
setupFiles: ["@flare-city/test/setup"],
},
});
Loading

0 comments on commit 4e5a38f

Please sign in to comment.