Skip to main content
Version: Next (2.x)

Introduction

Building GraphQL APIs doesn't have to be complex. Baeta helps developers focus on what matters most — creating clear, maintainable APIs with less boilerplate.

What is Baeta?

Baeta is a modular, type-safe GraphQL framework built around a schema-first approach. Instead of mixing schema definitions with code, you define your types using GraphQL SDL, keeping your API design clear and easy to reason about.

Large schemas can quickly become hard to manage, so Baeta encourages you to split your schema into modules. Each module defines its own types and resolvers, keeping your API organized as it grows.

It automatically generates types from your schemas, giving you fully type-safe resolvers with no extra code.

Why use Baeta?

Readable and maintainable: A schema-first workflow makes your API easy to understand and update over time.

Modular and scalable: Split large schemas into smaller modules that are easy to extend and maintain.

Type-safe by default: Automatic type generation ensures your resolvers always match your schema.

Flexible: Add only what you need — extend functionality with optional plugins and extensions like:

  • @baeta/extension-auth: Granular scope-based authorization
  • @baeta/extension-cache: Automatic caching with simple utilities for updating the cache
  • @baeta/extension-complexity: Query complexity analysis to protect against expensive queries

How it Works

1. Define your schema

type User {
id: ID!
name: String!
email: String!
age: Int
}

input UserWhereUnique {
id: ID
email: String
}

type Query {
user(where: UserWhereUnique!): User!
users: [User!]!
}

2. Implement your resolvers

import { UserModule } from "./typedef.ts";

const { Query } = UserModule;

const userQuery = Query.user.resolve(({ args }) => {
return dataSource.user.find(args.where);
});

const usersQuery = Query.users.resolve(() => {
return dataSource.user.findMany();
});

export default Query.$fields({
user: userQuery,
users: usersQuery,
});

3. Add authorization

import { UserModule } from "./typedef.ts";

const { Query } = UserModule;

const userQuery = Query.user
.$auth({
$or: {
isPublic: true,
isLoggedIn: true,
},
})
.resolve(async ({ args }) => {
// ...
});

4. Add caching

import { defineQuery } from "@baeta/cache";

const { Query, Mutation, User } = UserModule;

export const userCache = User.$createCache()
.withQueries({
findUser: defineQuery({
resolve: async (args: { id?: string }) => {
return dataSource.user.find(args);
},
}),
})
.build();

const userQuery = Query.user
.$auth({
// ...
})
.$resolveCache(userCache.queries.findUser, ({ args }) => ({
id: args.where.id,
}));

const updateUserMutation = Mutation.updateUser
.$use(async (next) => {
const user = await next();
await userCache.update(user);
return user;
})
.resolve(async ({ args }) => {
// ...
});

Compatibility

Baeta is just a schema builder, which makes it compatible with all GraphQL servers. It works seamlessly with popular libraries such as Graphql Yoga and Apollo Server, as well as other popular tools like Prisma, Drizzle and Kysely.

Baeta's development tools are built for Node.js, but the runtime code is environment-agnostic. This means your Baeta applications can run anywhere JavaScript runs, including:

  • Deno
  • Cloudflare Workers
  • AWS Lambda
  • Vercel Edge Functions
  • Bun
  • Node.js
  • Any other JavaScript runtime

Ready to get started?

Head over to the the next page to learn more and to get started!