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
- Explore GraphQL Yoga documentation
- Learn about GraphQL schema design
- Check out GraphQL best practices