add zod validated configuration and context

This commit is contained in:
Benjamin Palko 2024-12-03 12:01:27 -05:00
parent aa2fc6e89a
commit b645af1337
11 changed files with 76 additions and 27 deletions

2
.env Normal file
View file

@ -0,0 +1,2 @@
APP_VERSION=1.0.0-alpha
DATABASE_URL="file:./dev.db"

2
.gitignore vendored
View file

@ -1,3 +1 @@
node_modules node_modules
# Keep environment variables out of version control
.env

View file

@ -19,3 +19,4 @@ bun run src/index.ts
- **Pothos** - **Pothos**
- **Prisma** Database ORM - **Prisma** Database ORM
- **Pino** Logger - **Pino** Logger
- **Zod** Schema validation

BIN
bun.lockb

Binary file not shown.

View file

@ -20,6 +20,7 @@
"graphql": "^16.9.0", "graphql": "^16.9.0",
"graphql-yoga": "^5.10.4", "graphql-yoga": "^5.10.4",
"pino": "^9.5.0", "pino": "^9.5.0",
"pino-pretty": "^13.0.0" "pino-pretty": "^13.0.0",
"zod": "^3.23.8"
} }
} }

22
src/config/index.ts Normal file
View file

@ -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,
};
};

3
src/prisma/index.ts Normal file
View file

@ -0,0 +1,3 @@
import { PrismaClient } from "@prisma/client";
export const prisma = new PrismaClient();

29
src/yoga/builder.ts Normal file
View file

@ -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<typeof prisma>;
}>({
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",
},
});

10
src/yoga/context.ts Normal file
View file

@ -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,
};
};

View file

@ -1,8 +1,10 @@
import { yogaLogger } from "@lib/logger"; import { yogaLogger } from "@lib/logger";
import { createYoga } from "graphql-yoga"; import { createYoga } from "graphql-yoga";
import { context } from "./context";
import { schema } from "./schema"; import { schema } from "./schema";
export const yoga = createYoga({ export const yoga = createYoga({
schema, schema,
context: context,
logging: yogaLogger, logging: yogaLogger,
}); });

View file

@ -1,27 +1,5 @@
import SchemaBuilder from "@pothos/core"; import { prisma } from "@app/prisma";
import PrismaPlugin, { import { builder } from "./builder";
type PrismaTypesFromClient,
} from "@pothos/plugin-prisma";
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
const builder = new SchemaBuilder<{
PrismaTypes: PrismaTypesFromClient<typeof prisma>;
}>({
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",
},
});
const User = builder.prismaObject("User", { const User = builder.prismaObject("User", {
fields: (t) => ({ fields: (t) => ({
@ -44,6 +22,9 @@ const Post = builder.prismaObject("Post", {
builder.queryType({ builder.queryType({
fields: (t) => ({ fields: (t) => ({
version: t.string({
resolve: (parent, args, context) => context.config.app_version,
}),
users: t.prismaField({ users: t.prismaField({
type: [User], type: [User],
resolve: async () => { resolve: async () => {