From c68f517484410e41a7833b9dd6e060e1dad17c94 Mon Sep 17 00:00:00 2001 From: Vicary A Date: Sat, 13 Apr 2024 12:38:50 +0800 Subject: [PATCH] feat: built-in endpoint handler --- README.md | 34 ++++++++++--------------- cli.ts | 26 +++++++++++++++++++ deno.json | 2 +- deps.ts | 2 ++ examples/graphql-yoga/routes/graphql.ts | 18 ++----------- mod.ts | 1 + schema.ts | 2 +- server.ts | 26 +++++++++++++++++++ 8 files changed, 72 insertions(+), 39 deletions(-) create mode 100644 server.ts diff --git a/README.md b/README.md index 9638c04..9cf4c00 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,10 @@ A simple GraphQL server for Deno Fresh. ## Why `fresh-graphql`? -1. Familiar developer experience with `fresh` projects. -1. `deno deploy` has no dynamic imports. -1. [`tRPC`](https://trpc.io) doesn't support deno (yet). +1. You want file based routing. +1. You want GraphQL. +1. You have a [Deno Fresh](https://fresh.deno.dev) project. +1. `deno deploy` does not support dynamic imports. ## Installation @@ -54,36 +55,27 @@ await dev(import.meta.url, "./main.ts"); ## Usage +1. Run `deno task start` +1. Visit `http://localhost:3000/graphql` for an GraphiQL interface. + ### Entrypoint -Any data handler routes would work, the example below uses the pathname -`/graphql` as a convension. +Our CLI will generate a default GraphQL endpoint for you, you may also do it +manually by copying the contents below: ```ts // routes/graphql.ts -import { type FreshContext } from "$fresh/server.ts"; -import { fromManifest } from "@vicary/fresh-graphql"; -import { createYoga } from "graphql-yoga"; +import { createHandler } from "@vicary/fresh-graphql"; import manifest from "../graphql.gen.ts"; -// FRSH_GQL_DEV is set when you start the GraphQL development server in dev.ts. -const debug = Deno.env.has("FRSH_GQL_DEV"); - -const yoga = createYoga({ - graphiql: debug, - logging: debug, - maskedErrors: !debug, - schema: fromManifest(manifest), -}); - -export const handler = async (req: Request, ctx: FreshContext) => { - return await yoga.handleRequest(req, ctx); -}; +export const handler = createHandler(manifest); ``` ### Queries and Mutations +This is a GraphQL version of the joke API when you create a new Fresh project. + ```ts // graphql/Query/joke.ts diff --git a/cli.ts b/cli.ts index 682c987..b6c8e2e 100644 --- a/cli.ts +++ b/cli.ts @@ -61,6 +61,14 @@ async function main() { log.error("Unable to patch deno.json, please try to do it manually."); } } + + if (promptYesNo("Create default GraphQL endpoint at routes/graphql?", true)) { + if (!await createEndpoint("routes/graphql.ts")) { + log.error( + "Unable to create routes/graphql.ts, please try to do it manually.", + ); + } + } } await main(); @@ -202,3 +210,21 @@ async function patchDenoJson(): Promise { return true; } + +async function createEndpoint(path: string) { + try { + await Deno.writeTextFile( + path, + `// ${path}: GraphQL endpoint for fresh-graphql +import { createHandler } from "@vicary/fresh-graphql"; +import manifest from "../graphql.gen.ts"; + +export const handler = createHandler(manifest); +`, + ); + } catch { + return false; + } + + return true; +} diff --git a/deno.json b/deno.json index 171ddfc..b16649a 100644 --- a/deno.json +++ b/deno.json @@ -1,6 +1,6 @@ { "name": "@vicary/fresh-graphql", - "version": "0.2.9", + "version": "0.2.10-alpha.0", "exports": "./mod.ts", "publish": { "exclude": [ diff --git a/deps.ts b/deps.ts index d1ec7d2..9f4d866 100644 --- a/deps.ts +++ b/deps.ts @@ -1,3 +1,4 @@ +export type { FreshContext } from "https://deno.land/x/fresh@1.6.8/server.ts"; export { assert } from "jsr:@std/assert@^0.219.1/assert"; export * as colors from "jsr:@std/fmt@^0.219.1/colors"; export { ensureDir } from "jsr:@std/fs@^0.219.1/ensure_dir"; @@ -32,6 +33,7 @@ export { type SchemaMapper, type UnionTypeMapper, } from "npm:@graphql-tools/utils@^10.1.0"; +export { createYoga } from "npm:graphql-yoga@^5.3.0"; export { defaultFieldResolver, type DirectiveDefinitionNode, diff --git a/examples/graphql-yoga/routes/graphql.ts b/examples/graphql-yoga/routes/graphql.ts index cd65db7..beab4bd 100644 --- a/examples/graphql-yoga/routes/graphql.ts +++ b/examples/graphql-yoga/routes/graphql.ts @@ -1,18 +1,4 @@ -import { type FreshContext } from "$fresh/server.ts"; -import { fromManifest } from "@vicary/fresh-graphql"; -import { createYoga } from "graphql-yoga"; +import { createHandler } from "@vicary/fresh-graphql"; import manifest from "../graphql.gen.ts"; -// FRSH_GQL_DEV is set when you start the GraphQL development server in dev.ts. -const debug = Deno.env.has("FRSH_GQL_DEV"); - -const yoga = createYoga({ - graphiql: debug, - logging: debug, - maskedErrors: !debug, - schema: fromManifest(manifest), -}); - -export const handler = async (req: Request, ctx: FreshContext) => { - return await yoga.handleRequest(req, ctx); -}; +export const handler = createHandler(manifest); diff --git a/mod.ts b/mod.ts index a3db004..b3e0c4d 100644 --- a/mod.ts +++ b/mod.ts @@ -5,6 +5,7 @@ export { type GraphQLTypeModule, type Manifest, } from "./schema.ts"; +export { createHandler } from "./server.ts"; // Start interactive shell that automatically patches the fresh project. if (import.meta.main) { diff --git a/schema.ts b/schema.ts index 177ae05..42c4be0 100644 --- a/schema.ts +++ b/schema.ts @@ -18,7 +18,7 @@ import { type InterfaceTypeMapper, type IResolvers, makeExecutableSchema, - MapperKind, + type MapperKind, mapSchema, type ObjectTypeMapper, type ScalarTypeMapper, diff --git a/server.ts b/server.ts new file mode 100644 index 0000000..22ecaad --- /dev/null +++ b/server.ts @@ -0,0 +1,26 @@ +import { createYoga, type FreshContext } from "./deps.ts"; +import type { Manifest } from "./schema.ts"; +import { fromManifest } from "./schema.ts"; + +export type CreateHandlerOptions = { + debug?: boolean; +}; + +export function createHandler( + manifest: TManifest, + options?: CreateHandlerOptions, +) { + // FRSH_GQL_DEV is set when you start the GraphQL development server in dev.ts. + const debug = options?.debug ?? Deno.env.has("FRSH_GQL_DEV"); + + const yoga = createYoga({ + graphiql: debug, + logging: debug, + maskedErrors: !debug, + schema: fromManifest(manifest), + }); + + return async (req: Request, ctx: FreshContext) => { + return await yoga.handleRequest(req, ctx); + }; +}