DEV Community

Alex Spinov
Alex Spinov

Posted on

Pothos Has a Free API — Type-Safe GraphQL Schema Builder for TypeScript

Pothos is the plugin-based GraphQL schema builder for TypeScript — define your entire GraphQL API with full type safety, no code generation, no SDL files. Free and open source.

Why Pothos?

The problem with GraphQL in TypeScript: you write SDL strings, then TypeScript types, then resolvers — keeping them in sync is painful. Code generators like GraphQL Code Generator help, but add build steps.

Pothos solves this: your TypeScript code IS the schema. Types flow automatically.

  • No code generation — types are inferred from your code
  • No SDL files — schema defined in TypeScript
  • Plugin system — Prisma, Relay, Auth, Validation, and more
  • Works with any server — GraphQL Yoga, Apollo, Mercurius

Quick Start

npm install @pothos/core graphql
Enter fullscreen mode Exit fullscreen mode
import SchemaBuilder from "@pothos/core";

const builder = new SchemaBuilder({});

// Define a type
builder.objectType("User", {
  fields: (t) => ({
    id: t.exposeID("id"),
    name: t.exposeString("name"),
    email: t.exposeString("email"),
    posts: t.field({
      type: ["Post"],
      resolve: (user) => db.post.findMany({ where: { authorId: user.id } }),
    }),
  }),
});

builder.objectType("Post", {
  fields: (t) => ({
    id: t.exposeID("id"),
    title: t.exposeString("title"),
    content: t.exposeString("content"),
    author: t.field({
      type: "User",
      resolve: (post) => db.user.findUnique({ where: { id: post.authorId } }),
    }),
  }),
});

// Define queries
builder.queryType({
  fields: (t) => ({
    users: t.field({
      type: ["User"],
      resolve: () => db.user.findMany(),
    }),
    user: t.field({
      type: "User",
      nullable: true,
      args: { id: t.arg.id({ required: true }) },
      resolve: (_, { id }) => db.user.findUnique({ where: { id } }),
    }),
  }),
});

// Define mutations
builder.mutationType({
  fields: (t) => ({
    createUser: t.field({
      type: "User",
      args: {
        name: t.arg.string({ required: true }),
        email: t.arg.string({ required: true }),
      },
      resolve: (_, { name, email }) => db.user.create({ data: { name, email } }),
    }),
  }),
});

// Build the schema
export const schema = builder.toSchema();
Enter fullscreen mode Exit fullscreen mode

Prisma Plugin (Zero-Effort CRUD)

npm install @pothos/plugin-prisma
Enter fullscreen mode Exit fullscreen mode
import SchemaBuilder from "@pothos/core";
import PrismaPlugin from "@pothos/plugin-prisma";
import type PrismaTypes from "@pothos/plugin-prisma/generated";
import { PrismaClient } from "@prisma/client";

const prisma = new PrismaClient();

const builder = new SchemaBuilder<{
  PrismaTypes: PrismaTypes;
}>({
  plugins: [PrismaPlugin],
  prisma: { client: prisma },
});

// One line per model — Pothos reads your Prisma schema!
builder.prismaObject("User", {
  fields: (t) => ({
    id: t.exposeID("id"),
    name: t.exposeString("name"),
    email: t.exposeString("email"),
    posts: t.relation("posts"),        // Automatic relation
    postCount: t.relationCount("posts"), // Automatic count
  }),
});

builder.prismaObject("Post", {
  fields: (t) => ({
    id: t.exposeID("id"),
    title: t.exposeString("title"),
    author: t.relation("author"),
  }),
});

builder.queryType({
  fields: (t) => ({
    // Automatic Prisma query with type safety
    users: t.prismaField({
      type: ["User"],
      resolve: (query) => prisma.user.findMany({ ...query }),
    }),
  }),
});
Enter fullscreen mode Exit fullscreen mode

Auth Plugin

import AuthPlugin from "@pothos/plugin-scope-auth";

const builder = new SchemaBuilder<{
  Context: { user?: { id: string; role: string } };
  AuthScopes: {
    logged: boolean;
    admin: boolean;
  };
}>({
  plugins: [AuthPlugin],
  authScopes: (ctx) => ({
    logged: !!ctx.user,
    admin: ctx.user?.role === "admin",
  }),
});

// Protected query
builder.queryField("myProfile", (t) =>
  t.field({
    type: "User",
    authScopes: { logged: true },
    resolve: (_, __, ctx) => db.user.findUnique({ where: { id: ctx.user!.id } }),
  })
);

// Admin-only mutation
builder.mutationField("deleteUser", (t) =>
  t.field({
    type: "Boolean",
    authScopes: { admin: true },
    args: { userId: t.arg.id({ required: true }) },
    resolve: async (_, { userId }) => {
      await db.user.delete({ where: { id: userId } });
      return true;
    },
  })
);
Enter fullscreen mode Exit fullscreen mode

Validation Plugin (Zod Integration)

import ValidationPlugin from "@pothos/plugin-validation";

const builder = new SchemaBuilder({
  plugins: [ValidationPlugin],
});

builder.mutationType({
  fields: (t) => ({
    createUser: t.field({
      type: "User",
      args: {
        name: t.arg.string({ required: true }),
        email: t.arg.string({ required: true }),
      },
      validate: {
        // Zod-like validation
        name: { minLength: 2, maxLength: 50 },
        email: { email: true },
      },
      resolve: (_, args) => db.user.create({ data: args }),
    }),
  }),
});
Enter fullscreen mode Exit fullscreen mode

Pothos vs Nexus vs TypeGraphQL vs SDL-first

Feature Pothos Nexus TypeGraphQL SDL-first
Type safety Full inference Full inference Decorators Code generation
Code generation None needed None needed None needed Required
Prisma integration Plugin Plugin TypeORM Manual
Auth Plugin Manual Decorators Manual
Relay support Plugin Plugin Manual Manual
Actively maintained Yes (2026) Slowing Yes N/A

Need to scrape data from any website and get it in structured JSON? Check out my web scraping tools on Apify — no coding required, results in minutes.

Have a custom data extraction project? Email me at spinov001@gmail.com — I build tailored scraping solutions for businesses.

Top comments (0)