Episode 30 compares two approaches to building schemas: schema-first with SDL and code-first with libraries like Type-GraphQL and Pothos. You'll learn class-based schemas with decorators, dependency injection, Pothos plugins, and a guide to choosing based on your team and project needs.

So far you've been writing schemas as separate SDL files — that's the schema-first approach. Episode 30 introduces the alternative: code-first, where the schema is generated directly from TypeScript code.
We'll compare the two philosophies, practice Type-GraphQL with decorators and dependency injection, look at Pothos as the modern successor, then close with a guide to choosing the right approach.
The schema-first approach (which you used in episodes 3-7) makes the SDL file the source of truth, then attaches resolvers to the schema:
type Query {
users: [User!]!
}
type User {
id: ID!
username: String!
}Its strengths: the schema is easy for non-programmers to review, frontend collaboration can start before the backend (via mocks, episode 29), and it's language-agnostic. Its weaknesses: duplication between SDL and TypeScript types, plus manually written resolver maps that are prone to type errors without codegen.
Modern schema-first is usually paired with GraphQL Code Generator (episode 23) so TypeScript types are generated automatically from SDL — overcoming most of its weaknesses.
The code-first approach reverses the direction: you write TypeScript classes with decorators, and the GraphQL schema is generated from that code. No duplication — the TypeScript types are the single source of truth:
npm install type-graphql graphql class-validator reflect-metadataimport { ObjectType, Field, ID } from "type-graphql";
@ObjectType()
export class User {
@Field(() => ID)
id: string;
@Field()
username: string;
}Note that @Field() defines the schema field, and resolvers are also methods in classes:
import { Resolver, Query, Arg } from "type-graphql";
@Resolver()
export class UserResolver {
@Query(() => User)
async user(@Arg("id") id: string) {
return this.userService.findById(id);
}
}Class resolvers enable dependency injection — dependencies are injected into the constructor, making the code easy to test:
import { Service } from "typedi";
@Service()
@Resolver()
export class UserResolver {
constructor(private userService: UserService) {}
}Build the schema from all resolvers:
import { buildSchema } from "type-graphql";
const schema = await buildSchema({
resolvers: [UserResolver, PostResolver],
container: Container,
});With Container from typedi, the whole dependency graph is managed automatically. This is why Type-GraphQL is very popular for large, structured applications.
Pothos is a newer code-first approach with advanced type safety and a plugin system:
npm install @pothos/coreimport SchemaBuilder from "@pothos/core";
const builder = new SchemaBuilder({});
builder.objectType("User", {
fields: (t) => ({
id: t.exposeID("id"),
username: t.exposeString("username"),
posts: t.field({
type: [Post],
resolve: (user) => db.posts.findByAuthor(user.id),
}),
}),
});
const schema = builder.toSchema();Pothos excels at type inference — resolver types are automatically inferred from the implementation — and has plugins for prisma, relay, scope auth, and many more. Migration from Nexus is fairly straightforward because the writing patterns are similar.
schema-first -> cross-team collaboration, mixed languages
code-first -> full TypeScript, large structure, DIAdditional considerations: team size and project complexity. Small teams on full TypeScript tend to choose code-first; enterprises with many consumers and strict schema review often choose schema-first plus codegen. There's no absolutely wrong answer — consistency matters more than the choice itself.
Key takeaways:
In the next episode, episode 31, you'll learn about deploying GraphQL to production — deployment platforms like Vercel, Railway, and Render, containerization with multi-stage Docker, Kubernetes, serverless considerations like cold starts, environment and secrets management, and database migration strategies. Your API will step into production!