diff --git a/.env b/.env new file mode 100644 index 0000000..c98868d --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +APP_VERSION=1.0.0-alpha +DATABASE_URL="file:./dev.db" diff --git a/.gitignore b/.gitignore index 11ddd8d..3c3629e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1 @@ node_modules -# Keep environment variables out of version control -.env diff --git a/README.md b/README.md index ae6cb47..294f769 100644 --- a/README.md +++ b/README.md @@ -19,3 +19,4 @@ bun run src/index.ts - **Pothos** - **Prisma** Database ORM - **Pino** Logger +- **Zod** Schema validation diff --git a/bun.lockb b/bun.lockb index 6570e5b..196c00f 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 5a89fa5..495f3ef 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "graphql": "^16.9.0", "graphql-yoga": "^5.10.4", "pino": "^9.5.0", - "pino-pretty": "^13.0.0" + "pino-pretty": "^13.0.0", + "zod": "^3.23.8" } } diff --git a/src/config/index.ts b/src/config/index.ts new file mode 100644 index 0000000..f4c0bc5 --- /dev/null +++ b/src/config/index.ts @@ -0,0 +1,22 @@ +import { logger } from "@lib/logger"; +import { z } from "zod"; + +export interface Configuration { + app_version: string; +} + +export const LoadConfig = (): Configuration => { + const { success, data, error } = z + .object({ + APP_VERSION: z.string().default("development"), + }) + .safeParse(process.env); + + if (!success) { + logger.error(error.message); + } + + return { + app_version: data!.APP_VERSION, + }; +}; diff --git a/src/prisma/index.ts b/src/prisma/index.ts new file mode 100644 index 0000000..901f3a0 --- /dev/null +++ b/src/prisma/index.ts @@ -0,0 +1,3 @@ +import { PrismaClient } from "@prisma/client"; + +export const prisma = new PrismaClient(); diff --git a/src/yoga/builder.ts b/src/yoga/builder.ts new file mode 100644 index 0000000..565bdc3 --- /dev/null +++ b/src/yoga/builder.ts @@ -0,0 +1,29 @@ +import type { Configuration } from "@app/config"; +import { prisma } from "@app/prisma"; +import SchemaBuilder from "@pothos/core"; +import PrismaPlugin, { + type PrismaTypesFromClient, +} from "@pothos/plugin-prisma"; +import type { YogaInitialContext } from "graphql-yoga"; + +type Context = YogaInitialContext & { + config: Configuration; +}; + +export const builder = new SchemaBuilder<{ + Context: Context; + PrismaTypes: PrismaTypesFromClient; +}>({ + plugins: [PrismaPlugin], + prisma: { + client: prisma, + // defaults to false, uses /// comments from prisma schema as descriptions + // for object types, relations and exposed fields. + // descriptions can be omitted by setting description to false + exposeDescriptions: false, + // use where clause from prismaRelatedConnection for totalCount (defaults to true) + filterConnectionTotalCount: true, + // warn when not using a query parameter correctly + onUnusedQuery: process.env.NODE_ENV === "production" ? null : "warn", + }, +}); diff --git a/src/yoga/context.ts b/src/yoga/context.ts new file mode 100644 index 0000000..bc85aa7 --- /dev/null +++ b/src/yoga/context.ts @@ -0,0 +1,10 @@ +import { LoadConfig } from "@app/config"; +import type { YogaInitialContext } from "graphql-yoga"; + +export const context = (initialContext: YogaInitialContext) => { + const config = LoadConfig(); + return { + ...initialContext, + config, + }; +}; diff --git a/src/yoga/index.ts b/src/yoga/index.ts index 43ce842..8d113ba 100644 --- a/src/yoga/index.ts +++ b/src/yoga/index.ts @@ -1,8 +1,10 @@ import { yogaLogger } from "@lib/logger"; import { createYoga } from "graphql-yoga"; +import { context } from "./context"; import { schema } from "./schema"; export const yoga = createYoga({ schema, + context: context, logging: yogaLogger, }); diff --git a/src/yoga/schema.ts b/src/yoga/schema.ts index fad1003..f72cc19 100644 --- a/src/yoga/schema.ts +++ b/src/yoga/schema.ts @@ -1,27 +1,5 @@ -import SchemaBuilder from "@pothos/core"; -import PrismaPlugin, { - type PrismaTypesFromClient, -} from "@pothos/plugin-prisma"; -import { PrismaClient } from "@prisma/client"; - -const prisma = new PrismaClient(); - -const builder = new SchemaBuilder<{ - PrismaTypes: PrismaTypesFromClient; -}>({ - plugins: [PrismaPlugin], - prisma: { - client: prisma, - // defaults to false, uses /// comments from prisma schema as descriptions - // for object types, relations and exposed fields. - // descriptions can be omitted by setting description to false - exposeDescriptions: false, - // use where clause from prismaRelatedConnection for totalCount (defaults to true) - filterConnectionTotalCount: true, - // warn when not using a query parameter correctly - onUnusedQuery: process.env.NODE_ENV === "production" ? null : "warn", - }, -}); +import { prisma } from "@app/prisma"; +import { builder } from "./builder"; const User = builder.prismaObject("User", { fields: (t) => ({ @@ -44,6 +22,9 @@ const Post = builder.prismaObject("Post", { builder.queryType({ fields: (t) => ({ + version: t.string({ + resolve: (parent, args, context) => context.config.app_version, + }), users: t.prismaField({ type: [User], resolve: async () => {