Skip to main content

GraphQL

Balda.js provides built-in support for GraphQL through integration with GraphQL Yoga. GraphQL support is optional and requires installing peer dependencies.

Installation

First, install the required peer dependencies:

npm install graphql graphql-yoga

or with yarn:

yarn add graphql graphql-yoga

Basic Setup

Enable GraphQL by passing a graphql configuration object when creating your server:

import { Server } from 'balda-js';

const server = new Server({
port: 3000,
graphql: {
schema: {
typeDefs: `
type Query {
hello: String
}
`,
resolvers: {
Query: {
hello: () => 'Hello from GraphQL!',
},
},
},
},
});

await server.listen();

Your GraphQL endpoint will be available at /graphql by default.

Configuration Options

Schema Configuration

The schema option accepts type definitions and resolvers:

const server = new Server({
graphql: {
schema: {
typeDefs: `
type User {
id: ID!
name: String!
email: String!
}

type Query {
users: [User!]!
user(id: ID!): User
}

type Mutation {
createUser(name: String!, email: String!): User!
}
`,
resolvers: {
Query: {
users: () => {
// Fetch users from database
return [];
},
user: (parent, args) => {
// Fetch user by ID
return { id: args.id, name: 'John', email: 'john@example.com' };
},
},
Mutation: {
createUser: (parent, args) => {
// Create user in database
return { id: '1', name: args.name, email: args.email };
},
},
},
},
},
});

Yoga Options

You can pass additional options to GraphQL Yoga:

const server = new Server({
graphql: {
schema: {
typeDefs: `...`,
resolvers: {},
},
yogaOptions: {
graphqlEndpoint: '/graphql',
landingPage: true,
maskedErrors: false,
cors: {
origin: '*',
credentials: true,
},
},
},
});

For a full list of Yoga options, see the GraphQL Yoga documentation.

Dynamic Schema Building

Balda.js provides methods to add type definitions and resolvers dynamically:

Adding Type Definitions

Use addTypeDef() to add type definitions after server initialization:

const server = new Server({ graphql: {} });

// Add type definitions
server.graphql.addTypeDef(`
type Book {
id: ID!
title: String!
author: String!
}
`);

server.graphql.addTypeDef(`
extend type Query {
books: [Book!]!
book(id: ID!): Book
}
`);

You can also add multiple type definitions at once:

server.graphql.addTypeDef([
`type Author { id: ID!, name: String! }`,
`type Publisher { id: ID!, name: String! }`,
]);

Adding Resolvers

Use addResolver() to add resolvers dynamically:

// Add resolvers for a specific type
server.graphql.addResolver('Query', {
books: () => {
return [
{ id: '1', title: 'GraphQL Basics', author: 'John Doe' },
];
},
book: (parent, args) => {
return { id: args.id, title: 'Book Title', author: 'Author' };
},
});

server.graphql.addResolver('Mutation', {
createBook: (parent, args) => {
return { id: '1', title: args.title, author: args.author };
},
});

You can also add full resolver objects:

server.graphql.addResolver({
Query: {
users: () => [],
},
Mutation: {
createUser: (parent, args) => args,
},
});

For custom types:

server.graphql.addResolver('Book', {
author: (parent) => {
// Resolve nested author field
return { id: '1', name: parent.author };
},
});

Typed Context

Balda.js exports a GraphQLContext interface that you can extend to provide type-safe context in your resolvers.

Extending the Context

Use TypeScript's module augmentation to extend the context type:

import type { GraphQLContext } from 'balda-js';

// Define your custom context properties
interface User {
id: string;
name: string;
email: string;
}

interface Database {
users: Map<string, User>;
}

// Extend the GraphQLContext interface
declare module 'balda-js' {
interface GraphQLContext {
user?: User;
db: Database;
req: Request;
}
}

Using Typed Context in Resolvers

Once extended, your resolvers will have full type safety:

server.graphql.addResolver('Query', {
me: (parent, args, context) => {
// context.user is now fully typed!
if (!context.user) {
throw new Error('Not authenticated');
}
return context.user;
},
users: (parent, args, context) => {
// context.db is typed as Database
return Array.from(context.db.users.values());
},
});

Providing Context

Pass context values through the yogaOptions:

const db: Database = {
users: new Map(),
};

const server = new Server({
graphql: {
schema: {
typeDefs: `...`,
resolvers: {},
},
yogaOptions: {
context: async ({ request }) => {
// Extract user from request headers
const token = request.headers.get('authorization');
const user = token ? await verifyToken(token) : undefined;

return {
user,
db,
req: request,
};
},
},
},
});

Resolver Function Signature

All resolver functions follow the standard GraphQL signature:

type GraphQLResolverFunction<TContext = GraphQLContext> = (
parent: unknown,
args: Record<string, unknown>,
context: TContext,
info: unknown
) => unknown | Promise<unknown>;
  • parent: The result of the parent resolver (for nested fields)
  • args: The arguments provided to the field
  • context: The shared context object (typed via module augmentation)
  • info: GraphQL execution info (field name, parent type, etc.)

Resolvers can return values synchronously or return a Promise for async operations.

Error Handling

GraphQL Yoga automatically catches errors thrown in resolvers:

server.graphql.addResolver('Query', {
user: async (parent, args, context) => {
const user = await context.db.users.get(args.id);

if (!user) {
throw new Error('User not found');
}

return user;
},
});

For custom error handling, use Yoga's error masking options:

const server = new Server({
graphql: {
yogaOptions: {
maskedErrors: {
maskError: (error, message, isDev) => {
if (error.message.includes('INTERNAL')) {
return new Error('Internal server error');
}
return error;
},
},
},
},
});

GraphiQL Playground

GraphQL Yoga includes a built-in GraphiQL playground. Access it by navigating to your GraphQL endpoint in a browser:

http://localhost:3000/graphql

You can customize or disable the playground:

const server = new Server({
graphql: {
yogaOptions: {
// Enable/disable playground
landingPage: true,

// Or customize it
graphiql: {
title: 'My GraphQL API',
defaultQuery: `
query {
hello
}
`,
},
},
},
});

Lazy Loading

GraphQL dependencies are loaded lazily when the first GraphQL request is made. This means:

  • If GraphQL is not configured, the dependencies are never loaded

If the GraphQL peer dependencies are not installed and GraphQL is enabled, you'll receive a clear error message:

GraphQL is enabled but 'graphql-yoga' is not installed.
Install it with: npm install graphql graphql-yoga

Runtime Support

GraphQL support works across all Balda.js runtimes:

  • ✅ Node.js
  • ✅ Bun
  • ✅ Deno

The implementation uses dynamic imports to load GraphQL Yoga only when needed, ensuring optimal performance across all platforms.

Next Steps