Skip to content

GraphQL API Overview

The Banyan Platform automatically generates a complete GraphQL schema from all service contracts, with permission-based filtering for enhanced security.

Every service contract automatically generates GraphQL types:

  • Commands generate mutations with input types
  • Queries generate query fields with input types
  • Events generate subscription types
  • Permissions filter schema based on user access
  • Validation enforced from contract definitions

The schema dynamically filters based on user permissions. Users only see operations they can perform:

# User with permissions: ["users:read"]
type Query {
getUser(input: GetUserInput!): UserResult
listUsers(input: ListUsersInput!): ListUsersResult
}
# No mutations visible (no users:create permission)
# User with permissions: ["users:read", "users:create", "users:update"]
type Query {
getUser(input: GetUserInput!): UserResult
listUsers(input: ListUsersInput!): ListUsersResult
}
type Mutation {
createUser(input: CreateUserInput!): CreateUserResult
updateUser(input: UpdateUserInput!): UpdateUserResult
}
# Still no deleteUser (no users:delete permission)

This provides:

  • Better security - Users can’t discover unauthorized operations
  • Cleaner API - Schema shows only relevant operations
  • Developer experience - Auto-completion shows available actions

Access the GraphiQL interactive playground:

http://localhost:3003/graphql

Features:

  • Auto-completion and syntax highlighting
  • Interactive documentation explorer
  • Query history
  • Real-time validation
  • Permission-aware schema

All GraphQL requests go to:

POST http://localhost:3003/graphql
Content-Type: application/json
Authorization: Bearer <token>

Request body:

{
"query": "query GetUser($userId: String!) { ... }",
"variables": { "userId": "user-123" },
"operationName": "GetUser"
}

Commands represent state-changing operations:

@Command({
description: 'Create a new user',
permissions: ['users:create']
})
export class CreateUserCommand {
email!: string;
firstName!: string;
lastName?: string;
}

Generates:

input CreateUserInput {
email: String!
firstName: String!
lastName: String
}
type CreateUserResult {
userId: String!
email: String!
createdAt: String!
}
type Mutation {
createUser(input: CreateUserInput!): CreateUserResult
}

Queries represent read operations:

@Query({
description: 'Get user by ID',
permissions: ['users:read']
})
export class GetUserQuery {
userId!: string;
}

Generates:

input GetUserInput {
userId: String!
}
type UserResult {
userId: ID!
email: String!
firstName: String!
lastName: String
}
type Query {
getUser(input: GetUserInput!): UserResult
}

Events generate real-time subscriptions:

@DomainEvent('User.Events.UserCreated', { broadcast: true })
export class UserCreatedEvent {
userId!: string;
email!: string;
firstName!: string;
}

Generates:

type UserCreatedEvent {
userId: String!
email: String!
firstName: String!
timestamp: String!
}
type Subscription {
userCreated: UserCreatedEvent
}

GraphQL scalars map to TypeScript types:

GraphQLTypeScriptDescription
StringstringUTF-8 text
Intnumber32-bit integer
FloatnumberFloating point
Booleanbooleantrue/false
IDstringUnique identifier

Input types represent command/query parameters:

input CreateUserInput {
email: String! # Required
firstName: String! # Required
lastName: String # Optional
age: Int
active: Boolean
}

Object types represent results:

type UserResult {
userId: ID!
email: String!
firstName: String!
lastName: String
createdAt: String!
updatedAt: String
}

Lists represent collections:

type ListUsersResult {
users: [UserResult!]! # Non-null list of non-null users
totalCount: Int!
page: Int!
pageSize: Int!
}

Enums represent fixed sets of values:

enum UserRole {
ADMIN
USER
GUEST
}
input CreateUserInput {
role: UserRole!
}

Fetch data without side effects:

query GetUser {
getUser(input: { userId: "user-123" }) {
userId
email
firstName
}
}

Modify data:

mutation CreateUser {
createUser(input: {
email: "user@example.com"
firstName: "John"
lastName: "Doe"
}) {
userId
email
createdAt
}
}

Receive real-time events:

subscription OnUserCreated {
userCreated {
userId
email
firstName
}
}

All GraphQL requests require authentication (except public operations):

POST /graphql
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json

For local development only:

POST /graphql
X-Dev-User-Id: dev-user-123
X-Dev-Permissions: users:create,users:read
Content-Type: application/json

WARNING: Development mode bypasses all authentication. Never use in production.

GraphQL errors follow a standard format:

{
"errors": [
{
"message": "Validation failed",
"locations": [{ "line": 2, "column": 3 }],
"path": ["createUser"],
"extensions": {
"code": "VALIDATION_ERROR",
"details": {
"email": "Invalid email format"
}
}
}
],
"data": null
}

Common error codes:

  • VALIDATION_ERROR - Input validation failed
  • PERMISSION_DENIED - Insufficient permissions
  • NOT_FOUND - Resource not found
  • INTERNAL_ERROR - Server error

Queries are analyzed for complexity to prevent abuse:

Limits:

  • Max depth: 10 levels
  • Max fields: 100 per query
  • Max complexity: 1000 points

Example - too complex:

query TooComplex {
listUsers(input: { page: 1, pageSize: 100 }) {
users {
orders {
items {
product {
reviews {
user {
orders {
# Too deeply nested - rejected
}
}
}
}
}
}
}
}
}

DO:

query GetUser {
getUser(input: { userId: "123" }) {
userId
email
}
}

DON’T:

query GetUser {
getUser(input: { userId: "123" }) {
userId
email
firstName
lastName
address
phoneNumber
# Requesting unnecessary fields
}
}
fragment UserFields on UserResult {
userId
email
firstName
lastName
}
query GetUser {
getUser(input: { userId: "123" }) {
...UserFields
}
}
query ListUsers {
listUsers(input: { page: 1 }) {
users {
...UserFields
}
}
}

DO:

query GetUser($userId: String!) {
getUser(input: { userId: $userId }) {
userId
email
}
}

DON’T:

query GetUser {
getUser(input: { userId: "user-123" }) {
userId
email
}
}

Keep queries shallow for better performance:

DO:

query GetUserOrders {
getUser(input: { userId: "123" }) {
userId
}
getUserOrders(input: { userId: "123" }) {
orders {
orderId
}
}
}

DON’T:

query GetUserOrders {
getUser(input: { userId: "123" }) {
userId
orders {
items {
product {
reviews {
# Too nested
}
}
}
}
}
}
query GetUserDashboard {
getUser(input: { userId: "123" }) {
userId
email
}
getUserSettings(input: { userId: "123" }) {
theme
notifications
}
getUserOrders(input: { userId: "123" }) {
orders {
orderId
total
}
}
}

Request exactly the fields you need:

query GetUserEmails {
listUsers(input: { page: 1 }) {
users {
email # Only fetch emails
}
}
}

Schema provides compile-time validation and IDE auto-completion.

Built-in support for real-time updates:

subscription OnUserCreated {
userCreated {
userId
email
}
}