Schema First
Use GraphQL SDL to design modular, easy-to-maintain schemas.
Modular By Design
Split your code into small, manageable modules for better maintainability.
Type Safe
Out-of-the-box type safety with automatic code generation.
Flexible & Extensible
Use only what you need — extend with plugins anytime.
Define the Schema
Leverage GraphQL SDL to define schemas for each module, keeping your API organized and easy to maintain.
type User {
id: ID!
name: String!
email: String!
age: Int
}
input UserWhereUnique {
id: ID
email: String
}
type Query {
user(where: UserWhereUnique!): User!
users: [User!]!
}
Implement the Resolvers
Baeta generates and enforces types automatically, so you can focus on writing simple, reliable 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,
});
Compose and Extend
Create modular schemas that are easy to grow and maintain. Extend types naturally as your API expands.
type Photo {
id: ID!
url: String!
description: String!
postedBy: User!
}
input PhotoCreateData {
url: String! @trim
description: String!
userId: ID!
}
extend type User {
photos: [Photo!]!
}
Scope-Based Authorization
Secure your API with granular, scope-based authorization. Baeta makes permission handling simple and consistent.
import { UserModule } from "./typedef.ts";
const { Query } = UserModule;
const userQuery = Query.user
.$auth({
$or: {
isPublic: true,
isLoggedIn: true,
},
})
.resolve(async ({ args }) => {
// ...
});
Simple, Effective Caching
Add automatic caching to your queries with minimal setup. Update cached data easily and predictably when mutations occur.
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
.$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 }) => {
// ...
});
Powerful custom directives
Add custom behavior exactly where you need it. Create your own directives for validation, transformation, or any custom logic.
const trimDirective = createInputDirective({
name: "trim",
target: "scalar",
resolve: ({ getValue, setValue }) => {
const value = getValue();
if (typeof value === "string") {
setValue(value.trim());
}
},
});