Fix: GraphQL Yoga Not Working — Schema Errors, Resolvers Not Executing, or Subscriptions Failing
Quick Answer
How to fix GraphQL Yoga issues — schema definition, resolver patterns, context and authentication, file uploads, subscriptions with SSE, error handling, and Next.js integration.
The Problem
The GraphQL server starts but queries return null:
query {
users {
id
name
}
}
# Returns: { "data": { "users": null } }Or the schema fails to build:
Error: Type "User" not found in documentOr subscriptions don’t deliver real-time updates:
Subscription connects but never receives eventsWhy This Happens
GraphQL Yoga is a batteries-included GraphQL server built on top of the Envelop plugin system. Common issues:
- Every field in the schema needs a resolver — if you define a
usersquery in the schema but don’t implement a resolver for it, the field resolves tonull. GraphQL doesn’t throw for missing resolvers — it returns null. - Type definitions and resolvers must align — the schema defines the shape, resolvers provide the data. A field named
userNamein the schema butusernamein the resolver returns null for that field. - Context is rebuilt per request — authentication data, database connections, and other request-specific values go in the context function. If context returns an empty object, resolvers can’t access auth or database.
- Subscriptions use Server-Sent Events (SSE) by default — unlike Apollo which uses WebSockets, Yoga uses SSE for subscriptions. Client libraries must support SSE, not just WebSocket subscriptions.
Fix 1: Basic Server Setup
npm install graphql-yoga graphql// server.ts — standalone server
import { createSchema, createYoga } from 'graphql-yoga';
import { createServer } from 'node:http';
const yoga = createYoga({
schema: createSchema({
typeDefs: /* GraphQL */ `
type Query {
users: [User!]!
user(id: ID!): User
}
type Mutation {
createUser(input: CreateUserInput!): User!
updateUser(id: ID!, input: UpdateUserInput!): User!
deleteUser(id: ID!): Boolean!
}
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
createdAt: String!
}
type Post {
id: ID!
title: String!
body: String!
author: User!
}
input CreateUserInput {
name: String!
email: String!
}
input UpdateUserInput {
name: String
email: String
}
`,
resolvers: {
Query: {
users: async (_, __, context) => {
return context.db.query.users.findMany();
},
user: async (_, { id }, context) => {
return context.db.query.users.findFirst({
where: eq(users.id, id),
});
},
},
Mutation: {
createUser: async (_, { input }, context) => {
const [user] = await context.db.insert(users).values(input).returning();
return user;
},
updateUser: async (_, { id, input }, context) => {
const [user] = await context.db.update(users)
.set(input)
.where(eq(users.id, id))
.returning();
return user;
},
deleteUser: async (_, { id }, context) => {
await context.db.delete(users).where(eq(users.id, id));
return true;
},
},
// Field resolver — resolve nested relationships
User: {
posts: async (parent, _, context) => {
return context.db.query.posts.findMany({
where: eq(posts.authorId, parent.id),
});
},
},
Post: {
author: async (parent, _, context) => {
return context.db.query.users.findFirst({
where: eq(users.id, parent.authorId),
});
},
},
},
}),
// Context — available in all resolvers
context: async ({ request }) => {
const token = request.headers.get('authorization')?.replace('Bearer ', '');
const user = token ? await verifyToken(token) : null;
return {
db, // Database instance
user, // Authenticated user (or null)
request,
};
},
// GraphiQL explorer
graphiql: true,
});
const server = createServer(yoga);
server.listen(4000, () => console.log('GraphQL server on http://localhost:4000/graphql'));Fix 2: Next.js App Router Integration
// app/api/graphql/route.ts
import { createSchema, createYoga } from 'graphql-yoga';
import { db } from '@/lib/db';
const yoga = createYoga({
schema: createSchema({
typeDefs: /* GraphQL */ `
type Query {
hello: String!
posts(limit: Int): [Post!]!
}
type Post {
id: ID!
title: String!
body: String!
}
`,
resolvers: {
Query: {
hello: () => 'Hello from GraphQL Yoga!',
posts: async (_, { limit = 10 }) => {
return db.query.posts.findMany({ limit });
},
},
},
}),
graphqlEndpoint: '/api/graphql',
fetchAPI: { Response },
});
const { handleRequest } = yoga;
export {
handleRequest as GET,
handleRequest as POST,
handleRequest as OPTIONS,
};Fix 3: Authentication and Authorization
import { createSchema, createYoga } from 'graphql-yoga';
import { useGenericAuth } from '@envelop/generic-auth';
const yoga = createYoga({
schema: createSchema({
typeDefs: /* GraphQL */ `
type Query {
me: User
adminStats: Stats! # Should only be accessible to admins
}
`,
resolvers: {
Query: {
me: (_, __, context) => context.currentUser,
adminStats: (_, __, context) => {
if (!context.currentUser) throw new GraphQLError('Not authenticated');
if (context.currentUser.role !== 'admin') throw new GraphQLError('Not authorized');
return getAdminStats();
},
},
},
}),
context: async ({ request }) => {
const token = request.headers.get('authorization')?.replace('Bearer ', '');
let currentUser = null;
if (token) {
try {
const payload = await verifyJWT(token);
currentUser = await db.query.users.findFirst({
where: eq(users.id, payload.sub),
});
} catch {
// Invalid token — continue as unauthenticated
}
}
return { currentUser, db };
},
});Fix 4: Subscriptions with SSE
import { createSchema, createYoga, createPubSub } from 'graphql-yoga';
// Create a pub/sub instance
const pubSub = createPubSub<{
'message:created': [{ messageCreated: Message }];
'user:online': [{ userOnline: User }];
}>();
const yoga = createYoga({
schema: createSchema({
typeDefs: /* GraphQL */ `
type Query {
messages(channelId: ID!): [Message!]!
}
type Mutation {
sendMessage(channelId: ID!, text: String!): Message!
}
type Subscription {
messageCreated(channelId: ID!): Message!
}
type Message {
id: ID!
text: String!
channelId: ID!
author: User!
createdAt: String!
}
`,
resolvers: {
Query: {
messages: async (_, { channelId }, ctx) => {
return ctx.db.query.messages.findMany({
where: eq(messages.channelId, channelId),
orderBy: desc(messages.createdAt),
limit: 50,
});
},
},
Mutation: {
sendMessage: async (_, { channelId, text }, ctx) => {
if (!ctx.currentUser) throw new GraphQLError('Not authenticated');
const [message] = await ctx.db.insert(messages).values({
id: crypto.randomUUID(),
text,
channelId,
authorId: ctx.currentUser.id,
}).returning();
// Publish to subscribers
pubSub.publish('message:created', { messageCreated: message });
return message;
},
},
Subscription: {
messageCreated: {
subscribe: (_, { channelId }) =>
pubSub.subscribe('message:created'),
resolve: (payload) => payload.messageCreated,
},
},
},
}),
});
// Client — subscribe using SSE
// GraphQL Yoga uses Server-Sent Events, not WebSockets
// Most GraphQL clients support SSE:
// - graphql-sse
// - urql with @urql/exchange-sse
// - Apollo Client with SSE linkFix 5: File Uploads
import { createSchema, createYoga } from 'graphql-yoga';
const yoga = createYoga({
schema: createSchema({
typeDefs: /* GraphQL */ `
scalar File
type Query {
files: [FileInfo!]!
}
type Mutation {
uploadFile(file: File!): FileInfo!
uploadFiles(files: [File!]!): [FileInfo!]!
}
type FileInfo {
filename: String!
size: Int!
url: String!
}
`,
resolvers: {
Mutation: {
uploadFile: async (_, { file }: { file: File }) => {
const buffer = Buffer.from(await file.arrayBuffer());
const filename = file.name;
const size = buffer.length;
// Save to storage
const url = await uploadToStorage(buffer, filename);
return { filename, size, url };
},
uploadFiles: async (_, { files }: { files: File[] }) => {
return Promise.all(
files.map(async (file) => {
const buffer = Buffer.from(await file.arrayBuffer());
const url = await uploadToStorage(buffer, file.name);
return { filename: file.name, size: buffer.length, url };
})
);
},
},
},
}),
// File upload limits
multipart: {
maxFileSize: 10 * 1024 * 1024, // 10MB
maxFiles: 5,
},
});Fix 6: Error Handling
import { createSchema, createYoga } from 'graphql-yoga';
import { GraphQLError } from 'graphql';
const yoga = createYoga({
schema: createSchema({
resolvers: {
Mutation: {
createPost: async (_, { input }, ctx) => {
// Throw user-facing errors
if (!ctx.currentUser) {
throw new GraphQLError('You must be logged in', {
extensions: { code: 'UNAUTHENTICATED' },
});
}
if (input.title.length < 3) {
throw new GraphQLError('Title must be at least 3 characters', {
extensions: {
code: 'VALIDATION_ERROR',
field: 'title',
},
});
}
try {
return await ctx.db.insert(posts).values(input).returning();
} catch (error) {
// Log internal errors, return generic message
console.error('Database error:', error);
throw new GraphQLError('Failed to create post', {
extensions: { code: 'INTERNAL_ERROR' },
});
}
},
},
},
}),
// Global error masking
maskedErrors: {
isDev: process.env.NODE_ENV === 'development',
// In production, unexpected errors show "Unexpected error"
// In development, full error details are shown
},
});Still Not Working?
Query returns null instead of data — the resolver for that field is missing or returns undefined. Every field in your schema needs a resolver, either explicit or through a parent resolver that returns an object with matching property names. Check for typos between schema field names and resolver keys.
“Type not found in document” — you’re referencing a type in your schema that isn’t defined. Check that all types used in field definitions, input types, and return types are defined in typeDefs. Also check for circular imports if you split your schema across files.
Subscriptions connect but never receive data — Yoga uses Server-Sent Events (SSE). Your client must support SSE subscriptions. Apollo Client needs graphql-sse or a custom SSE link. Also verify that pubSub.publish() is called in the mutation that should trigger the subscription.
Context is empty in resolvers — the context function must return an object. If it returns undefined or throws, resolvers receive an empty context. Add error handling in the context function and ensure async operations are awaited.
For related API issues, see Fix: tRPC Not Working and Fix: Hono Not Working.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Pothos Not Working — Types Not Resolving, Plugin Errors, or Prisma Integration Failing
How to fix Pothos GraphQL schema builder issues — type-safe schema definition, object and input types, Prisma plugin, relay connections, auth scope plugin, and schema printing.
Fix: Fastify Not Working — 404, Plugin Encapsulation, and Schema Validation Errors
How to fix Fastify issues — route 404 from plugin encapsulation, reply already sent, FST_ERR_VALIDATION, request.body undefined, @fastify/cors, hooks not running, and TypeScript type inference.
Fix: Better Auth Not Working — Login Failing, Session Null, or OAuth Callback Error
How to fix Better Auth issues — server and client setup, email/password and OAuth providers, session management, middleware protection, database adapters, and plugin configuration.
Fix: BullMQ Not Working — Jobs Not Processing, Workers Not Starting, or Redis Connection Failing
How to fix BullMQ issues — queue and worker setup, Redis connection, job scheduling, retry strategies, concurrency, rate limiting, event listeners, and dashboard monitoring.