GraphQL API
Complete GraphQL API reference for querying and managing applications, hooks, settings, and assets.
Overview
The AppServer GraphQL API is available at:
HTTP/WebSocket: http://localhost:8080/graphql
Based on pkg/v2/presentation/graphql/schema/:
Features:
- Queries: Fetch apps, hooks, settings, assets, health, and metrics
- Mutations: Install/uninstall apps, trigger hooks, update settings
- Subscriptions: Real-time app state changes, hook events, system events
- Relay Pagination: Cursor-based pagination for list queries
- Type Safety: Strongly typed schema with validation
Access and Authentication
GraphQL Playground
Development Only:
URL: http://localhost:8080/graphql
Enabled when: APPSERVER_GRAPHQL_PLAYGROUND_ENABLED=true
WARNING: Disable in production for security!
Authentication
GraphQL queries require:
- User Authentication: Kratos session cookie (
ory_kratos_session) - App Authentication: Certificate-based authentication for app-to-app calls
- Authorization: OpenFGA permission checks for protected resources
Queries
Based on pkg/v2/presentation/graphql/schema/queries.graphql:
App Queries
app(name: String!): App
Fetch a single application by name.
Example:
query GetApp {
app(name: "todos-bff") {
id
name
state
registeredAt
installedAt
webApp {
ui {
mode
routeBase
navigation {
title
href
icon
requiredPermissions
}
}
api {
basePath
upstreamBaseURL
requiredPermissions
defaultRateLimit {
rpm
burst
}
}
}
dependencies {
app {
name
}
kind
}
}
}
Response:
{
"data": {
"app": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "todos-bff",
"state": "INSTALLED",
"registeredAt": "2024-01-15T10:30:00Z",
"installedAt": "2024-01-15T10:31:00Z",
"webApp": {
"ui": {
"mode": "FEDERATION",
"routeBase": "/apps/todos",
"navigation": [
{
"title": "My Tasks",
"href": "/apps/todos/list",
"icon": "task-icon.svg",
"requiredPermissions": ["todos:read"]
}
]
},
"api": {
"basePath": "/api/apps/todos",
"upstreamBaseURL": "http://todos-bff.svc:8080",
"requiredPermissions": ["todos:read", "todos:write"],
"defaultRateLimit": {
"rpm": 60,
"burst": 10
}
}
},
"dependencies": []
}
}
}
apps(filter: AppFilter, first: Int, after: String): AppConnection!
List applications with filtering and pagination.
Arguments:
filter- Optional filter criteriastate- Filter by single statestates- Filter by multiple statesnameContains- Search by name substringregisteredAfter- Filter by registration dateinstalledAfter- Filter by installation date
first- Number of results per pageafter- Cursor for pagination
Example:
query ListApps {
apps(
filter: { states: [INSTALLED], nameContains: "bff" }
first: 10
) {
edges {
node {
id
name
state
isInstalled
installedAt
}
cursor
}
pageInfo {
hasNextPage
endCursor
}
totalCount
}
}
Response:
{
"data": {
"apps": {
"edges": [
{
"node": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "todos-bff",
"state": "INSTALLED",
"isInstalled": true,
"installedAt": "2024-01-15T10:31:00Z"
},
"cursor": "YXJyYXljb25uZWN0aW9uOjA="
}
],
"pageInfo": {
"hasNextPage": false,
"endCursor": "YXJyYXljb25uZWN0aW9uOjA="
},
"totalCount": 1
}
}
}
Hook Queries
hook(id: ID!): Hook
Fetch a single hook by ID.
Example:
query GetHook {
hook(id: "hook-123") {
id
name
app {
name
}
async
totalExecutions
successRate
averageDurationMs
}
}
hooks(appName: String, filter: HookFilter): [Hook!]!
List hooks for an app or all apps.
Arguments:
appName- Filter by app name (optional)filter- Optional filter criteriaasync- Filter by async/syncnameContains- Search by name substring
Example:
query ListHooks {
hooks(appName: "todos-bff", filter: { async: false }) {
id
name
async
totalExecutions
successRate
averageDurationMs
registeredAt
}
}
Response:
{
"data": {
"hooks": [
{
"id": "hook-123",
"name": "app.installed",
"async": false,
"totalExecutions": 42,
"successRate": 0.98,
"averageDurationMs": 125.5,
"registeredAt": "2024-01-15T10:30:00Z"
}
]
}
}
Settings Queries
settings(appID: ID!): Settings
Fetch all settings for an application.
Example:
query GetSettings {
settings(appID: "550e8400-e29b-41d4-a716-446655440000") {
appID
schema
values
updatedAt
updatedBy
}
}
Response:
{
"data": {
"settings": {
"appID": "550e8400-e29b-41d4-a716-446655440000",
"schema": {
"type": "object",
"properties": {
"api_key": {
"type": "string",
"encrypted": true
}
}
},
"values": {
"api_key": "encrypted:base64..."
},
"updatedAt": "2024-01-15T12:00:00Z",
"updatedBy": "user:123"
}
}
}
settingValue(appID: ID!, key: String!): SettingValue
Fetch a single setting value.
Example:
query GetSettingValue {
settingValue(appID: "550e8400-e29b-41d4-a716-446655440000", key: "api_key") {
key
value
}
}
Asset Queries
asset(appName: String!, assetName: String!): Asset
Fetch asset metadata.
Example:
query GetAsset {
asset(appName: "todos-bff", assetName: "remoteEntry.js") {
name
mimeType
size
sha256
url
}
}
Response:
{
"data": {
"asset": {
"name": "remoteEntry.js",
"mimeType": "application/javascript",
"size": 51234,
"sha256": "abc123...",
"url": "/apps/todos-bff/assets/remoteEntry.js"
}
}
}
System Queries
health: HealthStatus!
Check system health.
Example:
query GetHealth {
health {
status
checks {
name
status
message
timestamp
}
timestamp
}
}
Response:
{
"data": {
"health": {
"status": "HEALTHY",
"checks": [
{
"name": "database",
"status": "HEALTHY",
"message": "Connected",
"timestamp": "2024-01-15T14:30:00Z"
},
{
"name": "redis",
"status": "HEALTHY",
"message": "Connected",
"timestamp": "2024-01-15T14:30:00Z"
},
{
"name": "rabbitmq",
"status": "HEALTHY",
"message": "Connected",
"timestamp": "2024-01-15T14:30:00Z"
}
],
"timestamp": "2024-01-15T14:30:00Z"
}
}
}
metrics: Metrics!
Get system metrics.
Example:
query GetMetrics {
metrics {
apps {
totalApps
registeredApps
installedApps
}
hooks {
totalHooks
totalExecutions
successRate
}
proxy {
totalRequests
requestsPerSecond
averageLatencyMs
errorRate
}
system {
uptime
cpuUsage
memoryUsage {
total
used
percentUsed
}
}
}
}
Mutations
Based on pkg/v2/presentation/graphql/schema/mutations.graphql:
App Lifecycle
installApp(name: String!): AppMutationResult!
Install an application and its dependencies.
Example:
mutation InstallApp {
installApp(name: "todos-bff") {
success
app {
id
name
state
installedAt
}
error {
code
message
}
}
}
Response (Success):
{
"data": {
"installApp": {
"success": true,
"app": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "todos-bff",
"state": "INSTALLED",
"installedAt": "2024-01-15T10:31:00Z"
},
"error": null
}
}
}
Response (Error):
{
"data": {
"installApp": {
"success": false,
"app": null,
"error": {
"code": "DEPENDENCY_NOT_FOUND",
"message": "Required dependency 'auth-service' not found",
"details": {
"missing": ["auth-service"]
}
}
}
}
}
uninstallApp(name: String!): AppMutationResult!
Uninstall an application.
Example:
mutation UninstallApp {
uninstallApp(name: "todos-bff") {
success
app {
name
state
uninstalledAt
}
error {
code
message
}
}
}
Hook Operations
triggerHook(name: String!, data: JSON): HookTriggerResult!
Manually trigger a hook.
Example:
mutation TriggerHook {
triggerHook(
name: "app.settings.updated"
data: { appName: "todos-bff", keys: ["api_key"] }
) {
success
results {
app {
name
}
success
data
error {
code
message
}
}
error {
code
message
}
}
}
Response:
{
"data": {
"triggerHook": {
"success": true,
"results": [
{
"app": {
"name": "todos-bff"
},
"success": true,
"data": {
"processed": true
},
"error": null
}
],
"error": null
}
}
}
Settings Operations
updateSettings(appID: ID!, settings: JSON!): SettingsMutationResult!
Update application settings.
Example:
mutation UpdateSettings {
updateSettings(
appID: "550e8400-e29b-41d4-a716-446655440000"
settings: {
api_key: "new-secret-key"
feature_enabled: true
}
) {
success
settings {
values
updatedAt
updatedBy
}
error {
code
message
}
}
}
Response:
{
"data": {
"updateSettings": {
"success": true,
"settings": {
"values": {
"api_key": "encrypted:base64...",
"feature_enabled": true
},
"updatedAt": "2024-01-15T15:00:00Z",
"updatedBy": "user:123"
},
"error": null
}
}
}
deleteSettings(appID: ID!): SettingsMutationResult!
Delete all settings for an application.
Example:
mutation DeleteSettings {
deleteSettings(appID: "550e8400-e29b-41d4-a716-446655440000") {
success
settings {
appID
}
error {
code
message
}
}
}
Subscriptions
Based on pkg/v2/presentation/graphql/schema/subscriptions.graphql:
Real-Time App State Changes
appStateChanged(filter: AppStateFilter): App!
Subscribe to app state changes.
Arguments:
filter- Optional filterstates- Only receive changes for these statesappNames- Only receive changes for these apps
Example:
subscription WatchAppState {
appStateChanged(
filter: {
states: [INSTALLED, UNINSTALLED]
appNames: ["todos-bff"]
}
) {
id
name
state
installedAt
uninstalledAt
}
}
Event Stream:
{
"data": {
"appStateChanged": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "todos-bff",
"state": "INSTALLED",
"installedAt": "2024-01-15T10:31:00Z",
"uninstalledAt": null
}
}
}
Hook Events
hookTriggered(hookName: String): HookEvent!
Subscribe to hook trigger events.
Example:
subscription WatchHooks {
hookTriggered(hookName: "app.installed") {
hook {
name
}
triggeredBy {
name
}
data
timestamp
}
}
Event Stream:
{
"data": {
"hookTriggered": {
"hook": {
"name": "app.installed"
},
"triggeredBy": {
"name": "todos-bff"
},
"data": {
"appName": "todos-bff",
"installTime": "2024-01-15T10:31:00Z"
},
"timestamp": "2024-01-15T10:31:00Z"
}
}
}
System Events
systemEvent(types: [SystemEventType!]): SystemEvent!
Subscribe to system events.
Event Types:
SERVER_STARTED- Server startedSERVER_STOPPING- Server gracefully stoppingERROR_OCCURRED- System error occurredCONFIG_CHANGED- Configuration changed
Example:
subscription WatchSystemEvents {
systemEvent(types: [ERROR_OCCURRED, CONFIG_CHANGED]) {
type
message
data
timestamp
}
}
Types
Based on pkg/v2/presentation/graphql/schema/types.graphql:
Core Types
App:
id- Unique identifier (UUID)name- App name (unique)state- Current state (UNREGISTERED, REGISTERED, INSTALLED, UNINSTALLED)dependencies- App dependencies with kind (HARD, SOFT)registeredAt- Registration timestampinstalledAt- Installation timestamp (nullable)webApp- Web app configuration (UI, API, assets)hooks- Registered hookssettings- App settings
WebApp:
assets- Uploaded assetsui- UI configuration (mode, entrypoint, navigation)api- API configuration (routes, rate limits, permissions)
Hook:
id- Unique identifiername- Hook name (e.g., "app.installed")app- Owner appasync- Async execution flagtotalExecutions- Execution countsuccessRate- Success percentageaverageDurationMs- Average duration
Settings:
appID- Owner app IDschema- JSON schema definitionvalues- Setting values (encrypted if marked)updatedAt- Last update timestampupdatedBy- User who updated
Enums
AppState:
enum AppState {
UNREGISTERED # Manifest submitted, not persisted
REGISTERED # Persisted in database
INSTALLED # Installed and active
UNINSTALLED # Previously installed, now removed
}
DependencyKind:
enum DependencyKind {
HARD # Required dependency (installation fails if missing)
SOFT # Optional dependency (installation continues if missing)
}
UIMode:
enum UIMode {
FEDERATION # Module Federation
WEB_COMPONENT # Web Component
ESM_COMPONENT # ESM Component
}
HTTPMethod:
enum HTTPMethod {
GET
POST
PUT
PATCH
DELETE
HEAD
OPTIONS
}
Error Handling
All mutations return a result type with:
success- Boolean success flagerror- Error object (if failed)code- Machine-readable error codemessage- Human-readable messagedetails- Additional error context (JSON)
Common Error Codes:
APP_NOT_FOUND- App does not existAPP_ALREADY_INSTALLED- App already installedDEPENDENCY_NOT_FOUND- Required dependency missingPERMISSION_DENIED- Insufficient permissionsVALIDATION_ERROR- Invalid input dataINTERNAL_ERROR- Server error
Best Practices
Query Optimization
✅ DO:
- Request only needed fields
- Use pagination for large lists
- Use fragments for reusable field sets
- Filter results server-side
❌ DON'T:
- Fetch all fields if not needed
- Request deeply nested data without necessity
- Perform client-side filtering on large datasets
Example with Fragments:
fragment AppBasic on App {
id
name
state
isInstalled
}
query ListApps {
apps(first: 10) {
edges {
node {
...AppBasic
}
}
}
}
query GetApp {
app(name: "todos-bff") {
...AppBasic
dependencies {
app {
...AppBasic
}
}
}
}
Real-Time Updates
✅ DO:
- Filter subscriptions to needed events
- Handle reconnections gracefully
- Unsubscribe when no longer needed
- Combine with queries for initial state
❌ DON'T:
- Subscribe to all events without filtering
- Ignore connection errors
- Keep unnecessary subscriptions open
Example:
// Fetch initial state
const { data } = await client.query({
query: GET_APPS
});
// Subscribe to changes
const subscription = client.subscribe({
query: WATCH_APP_STATE,
variables: {
filter: { states: ['INSTALLED'] }
}
}).subscribe({
next: (event) => {
// Update local state
updateApp(event.data.appStateChanged);
},
error: (err) => {
console.error('Subscription error:', err);
// Reconnect logic
}
});
// Cleanup
subscription.unsubscribe();
Error Handling
✅ DO:
- Check
successfield in mutations - Display user-friendly error messages
- Log detailed errors for debugging
- Implement retry logic for transient errors
Example:
const result = await client.mutate({
mutation: INSTALL_APP,
variables: { name: 'todos-bff' }
});
if (result.data.installApp.success) {
toast.success('App installed successfully');
} else {
const error = result.data.installApp.error;
if (error.code === 'DEPENDENCY_NOT_FOUND') {
toast.error(`Missing dependencies: ${error.details.missing.join(', ')}`);
} else {
toast.error(error.message);
}
console.error('Installation failed:', error);
}
Code References
| Component | File | Purpose |
|---|---|---|
| Schema | pkg/v2/presentation/graphql/schema/schema.graphql | Root schema definition |
| Queries | pkg/v2/presentation/graphql/schema/queries.graphql | Query definitions |
| Mutations | pkg/v2/presentation/graphql/schema/mutations.graphql | Mutation definitions |
| Subscriptions | pkg/v2/presentation/graphql/schema/subscriptions.graphql | Subscription definitions |
| Types | pkg/v2/presentation/graphql/schema/types.graphql | Type definitions |
| Resolvers | pkg/v2/presentation/graphql/resolvers/ | Query/mutation implementations |
Related Topics
- Hooks & Activity - Hook system integration
- Settings Service - Settings management
- HTTP Proxy & Routing - API proxy
- Local Development Stack - GraphQL Playground setup