This document covers how GraphQL works in SyndrDB, including query structure, mutations, security features, and unique implementation details.
SyndrDB implements a native GraphQL interface that provides an alternative to the SQL-like query language. Unlike traditional GraphQL implementations that run over HTTP, SyndrDB's GraphQL operates through the native TCP protocol with special command prefixes.
🔹 Native Protocol Integration: GraphQL queries sent via GRAPHQL:: prefix through TCP socket
🔹 Dynamic Schema Generation: Schemas automatically generated from bundle structures
🔹 Type-Safe Operations: Full type checking and validation against bundle schemas
🔹 Introspection Support: Complete __schema and __type queries for schema exploration
🔹 Multi-Layer Security: 5-layer security system with rate limiting and complexity analysis
🔹 Relay Pagination: Industry-standard cursor-based pagination
🔹 Advanced Filtering: Powerful WHERE clauses with logical operators
🔹 Mutation Support: Full CRUD operations with auto-generated input types
| Feature | Traditional GraphQL | SyndrDB GraphQL |
|---|---|---|
| Transport | HTTP/HTTPS | Native TCP Protocol |
| Command Format | HTTP POST with JSON body | GRAPHQL::{query: "..."} |
| Schema | Static schema files | Dynamic from bundle structures |
| Security | Application-level | 5-layer built-in protection |
| Pagination | Varies by implementation | Relay-spec compliant cursors |
| Filtering | Custom per implementation | Unified WHERE clause system |
| Integration | Separate from database | Direct bundle access |
SyndrDB GraphQL commands use a special prefix over the TCP connection:
GRAPHQL::{...JSON payload...}
{
"query": "GraphQL query string",
"operationName": "optional operation name",
"variables": {
"optional": "variables"
}
}
// Command sent to SyndrDB TCP socket:
GRAPHQL::{
"query": "{ databases { name } }"
}
GRAPHQL::{
"query": "query GetUser($id: String!) { user(id: $id) { name email } }",
"variables": {
"id": "user_123"
}
}
GraphQL responses follow the standard GraphQL response structure:
{
"data": {
"databases": [
{ "name": "primary" },
{ "name": "myapp" }
]
},
"errors": null
}
{
"data": null,
"errors": [
{
"message": "Field 'unknownField' doesn't exist on type 'User'",
"path": ["user", "unknownField"],
"locations": [
{ "line": 2, "column": 5 }
],
"extensions": {
"code": "INVALID_FIELD"
}
}
]
}
SyndrDB generates GraphQL schemas dynamically from your bundle structures. When you create or modify bundles, the GraphQL schema automatically updates to reflect the changes.
Bundle Definition:
CREATE BUNDLE "users" WITH FIELDS (
{"id", STRING, true, true},
{"name", STRING, true, false},
{"email", STRING, true, true},
{"age", INT, false, false}
);
Generated GraphQL Type:
type User {
id: String!
name: String!
email: String!
age: Int
}
type Query {
users(
limit: Int
offset: Int
where: WhereInput
orderBy: [OrderByInput!]
): [User!]!
}
SyndrDB GraphQL supports these scalar types:
| SyndrDB Type | GraphQL Type | Description |
|---|---|---|
| STRING | String |
Text data |
| INT | Int |
32-bit integer |
| FLOAT | Float |
Floating-point number |
| BOOLEAN | Boolean |
true/false |
| DATETIME | DateTime |
Custom scalar for timestamps |
| JSON | JSON |
Custom scalar for JSON objects |
| TEXT | String |
Large text data |
| BLOB | String |
Binary data (base64 encoded) |
| TIMESTAMP | DateTime |
Unix timestamp |
| Modifier | GraphQL Symbol | Meaning |
|---|---|---|
| Required | ! |
Field must have a value (not null) |
| Optional | (no symbol) | Field can be null |
| Array | [Type] |
Field contains multiple values |
| Non-null Array | [Type!]! |
Array cannot be null, elements cannot be null |
Query the schema itself using introspection:
{
__schema {
types {
name
kind
fields {
name
type {
name
kind
}
}
}
}
}
{
__type(name: "User") {
name
fields {
name
type {
name
kind
ofType {
name
kind
}
}
}
}
}
{
__schema {
queryType {
name
fields {
name
type {
name
}
}
}
}
}
Retrieve all documents from a bundle:
{
users {
id
name
email
}
}
Select only the fields you need:
{
users {
name
email
}
}
Benefit: Reduces data transfer and improves performance
{
users(limit: 10, offset: 0) {
id
name
email
}
}
{
users {
id
name
}
products {
id
title
price
}
}
Rename fields in the response:
{
activeUsers: users(where: { status: { eq: "active" } }) {
id
fullName: name
emailAddress: email
}
inactiveUsers: users(where: { status: { eq: "inactive" } }) {
id
name
}
}
Response:
{
"data": {
"activeUsers": [...],
"inactiveUsers": [...]
}
}
Reuse field selections:
fragment UserDetails on User {
id
name
email
createdAt
}
{
activeUsers: users(where: { status: { eq: "active" } }) {
...UserDetails
}
adminUsers: users(where: { role: { eq: "admin" } }) {
...UserDetails
}
}
Parameterize your queries:
query GetUsersByStatus($status: String!, $limit: Int) {
users(where: { status: { eq: $status } }, limit: $limit) {
id
name
status
}
}
Variables:
{
"status": "active",
"limit": 20
}
mutation CreateUser($input: CreateUserInput!) {
createUser(input: $input) {
id
name
email
createdAt
}
}
Variables:
{
"input": {
"name": "Alice Johnson",
"email": "alice@example.com",
"age": 28,
"status": "active"
}
}
mutation {
createDatabase(input: { name: "analytics" }) {
name
createdAt
}
}
mutation {
createBundle(input: {
name: "products"
database: "myapp"
fields: [
{ name: "id", type: STRING, required: true, unique: true }
{ name: "title", type: STRING, required: true }
{ name: "price", type: FLOAT, required: true }
{ name: "inStock", type: BOOLEAN, required: true }
]
}) {
name
database
documentCount
}
}
mutation UpdateUser($id: String!, $input: UpdateUserInput!) {
updateUser(id: $id, input: $input) {
id
name
email
updatedAt
}
}
Variables:
{
"id": "user_123",
"input": {
"name": "Alice Smith",
"status": "inactive"
}
}
mutation DeleteUser($id: String!) {
deleteUser(id: $id) {
success
deletedCount
}
}
Variables:
{
"id": "user_123"
}
mutation DeleteInactiveUsers {
deleteUsers(where: { status: { eq: "inactive" } }) {
deletedCount
}
}
All mutations return the affected data:
{
"data": {
"createUser": {
"id": "user_456",
"name": "Alice Johnson",
"email": "alice@example.com",
"createdAt": "2024-01-20T10:30:00Z"
}
}
}
SyndrDB GraphQL provides powerful filtering through the where argument.
input WhereInput {
# Field-level filters
fieldName: FieldFilter
# Logical operators
and: [WhereInput!]
or: [WhereInput!]
not: WhereInput
}
input FieldFilter {
eq: JSON # Equals
ne: JSON # Not equals
gt: JSON # Greater than
gte: JSON # Greater than or equal
lt: JSON # Less than
lte: JSON # Less than or equal
like: String # String pattern matching
in: [JSON!] # Value in array
notIn: [JSON!] # Value not in array
isNull: Boolean # Field is null
}
{
users(where: { status: { eq: "active" } }) {
id
name
}
}
{
users(where: { age: { gt: 18 } }) {
id
name
age
}
}
{
users(where: { email: { like: "%@example.com" } }) {
id
name
email
}
}
{
users(where: { status: { in: ["active", "pending"] } }) {
id
name
status
}
}
{
users(where: {
and: [
{ age: { gte: 18 } }
{ status: { eq: "active" } }
{ email: { like: "%@company.com" } }
]
}) {
id
name
age
email
}
}
{
users(where: {
or: [
{ status: { eq: "active" } }
{ role: { eq: "admin" } }
]
}) {
id
name
status
role
}
}
{
users(where: {
not: {
status: { eq: "deleted" }
}
}) {
id
name
status
}
}
{
users(where: {
and: [
{
or: [
{ age: { gte: 18 } }
{ role: { eq: "admin" } }
]
}
{
not: {
status: { eq: "suspended" }
}
}
{
email: { like: "%@company.com" }
}
]
}) {
id
name
age
role
status
}
}
Translation: Get users who are:
SyndrDB translates GraphQL filters into native WHERE clauses:
| GraphQL Filter | SyndrDB WHERE Clause |
|---|---|
{ age: { eq: 25 } } |
age = 25 |
{ age: { gt: 18 } } |
age > 18 |
{ age: { gte: 18 } } |
age >= 18 |
{ age: { lt: 65 } } |
age < 65 |
{ age: { lte: 65 } } |
age <= 65 |
{ age: { ne: 0 } } |
age != 0 |
{ email: { like: "%@company.com" } } |
email LIKE "%@company.com" |
{ status: { in: ["active", "pending"] } } |
status IN ("active", "pending") |
{ deleted: { isNull: true } } |
deleted IS NULL |
SyndrDB implements Relay-spec cursor-based pagination for efficient data retrieval.
✅ Stable: Results don't skip or duplicate when data changes
✅ Efficient: Scales to large datasets
✅ Standard: Relay spec is industry standard
✅ Bidirectional: Support forward and backward navigation
Paginated queries return a Connection type:
{
users(first: 10) {
edges {
node {
id
name
email
}
cursor
}
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
totalCount
}
}
{
"data": {
"users": {
"edges": [
{
"node": {
"id": "user_1",
"name": "Alice",
"email": "alice@example.com"
},
"cursor": "Y3Vyc29yOjE="
},
{
"node": {
"id": "user_2",
"name": "Bob",
"email": "bob@example.com"
},
"cursor": "Y3Vyc29yOjI="
}
],
"pageInfo": {
"hasNextPage": true,
"hasPreviousPage": false,
"startCursor": "Y3Vyc29yOjE=",
"endCursor": "Y3Vyc29yOjI="
},
"totalCount": 150
}
}
}
Get the first N items:
{
users(first: 10) {
edges {
node { id name }
cursor
}
pageInfo {
hasNextPage
endCursor
}
}
}
Get next page:
{
users(first: 10, after: "Y3Vyc29yOjEw") {
edges {
node { id name }
cursor
}
pageInfo {
hasNextPage
endCursor
}
}
}
Get the last N items:
{
users(last: 10) {
edges {
node { id name }
cursor
}
pageInfo {
hasPreviousPage
startCursor
}
}
}
Get previous page:
{
users(last: 10, before: "Y3Vyc29yOjEw") {
edges {
node { id name }
cursor
}
pageInfo {
hasPreviousPage
startCursor
}
}
}
| Argument | Type | Description |
|---|---|---|
| first | Int |
Number of items to fetch from start (forward) |
| after | String |
Cursor to start after (forward) |
| last | Int |
Number of items to fetch from end (backward) |
| before | String |
Cursor to start before (backward) |
| Field | Type | Description |
|---|---|---|
| hasNextPage | Boolean! |
More items exist after current page |
| hasPreviousPage | Boolean! |
Items exist before current page |
| startCursor | String |
Cursor of first item in page |
| endCursor | String |
Cursor of last item in page |
Cursors are opaque base64-encoded strings:
Encoded: "Y3Vyc29yOjE="
Decoded: "cursor:1"
Format: base64(documentID:index)
⚠️ Never manually construct cursors - always use cursors returned by the API.
Combine pagination with filtering:
{
users(
first: 20
where: { status: { eq: "active" } }
orderBy: [{ field: "createdAt", direction: DESC }]
) {
edges {
node {
id
name
status
createdAt
}
cursor
}
pageInfo {
hasNextPage
endCursor
}
totalCount
}
}
let cursor = null;
let allUsers = [];
while (true) {
const query = `
query GetUsers($cursor: String) {
users(first: 100, after: $cursor) {
edges {
node { id name email }
cursor
}
pageInfo {
hasNextPage
endCursor
}
}
}
`;
const result = await executeGraphQL(query, { cursor });
// Add to collection
allUsers.push(...result.data.users.edges.map(e => e.node));
// Check if more pages exist
if (!result.data.users.pageInfo.hasNextPage) {
break;
}
// Update cursor for next page
cursor = result.data.users.pageInfo.endCursor;
}
console.log(`Loaded ${allUsers.length} users`);
SyndrDB GraphQL implements 5 layers of security to protect against abuse and resource exhaustion.
┌─────────────────────────────────────────┐
│ Layer 5: Query Monitoring │ ← Metrics & Alerting
├─────────────────────────────────────────┤
│ Layer 4: Query Timeout │ ← Execution Time Limit
├─────────────────────────────────────────┤
│ Layer 3: Rate Limiting │ ← Requests Per Minute
├─────────────────────────────────────────┤
│ Layer 2: Depth Limiting │ ← Nesting Depth
├─────────────────────────────────────────┤
│ Layer 1: Complexity Analysis │ ← Query Cost Estimation
└─────────────────────────────────────────┘
Prevents expensive queries before execution.
Each query is assigned a complexity score based on:
{
users { # Depth 1, cost 1
id # +1
name # +1
posts { # Depth 2, relationship +5, multiply by 2
id # +1
title # +1
}
}
}
# Calculation:
# Level 1: 1 (users) + 1 (id) + 1 (name) = 3
# Level 2: (5 (relationship) + 1 (id) + 1 (title)) × 2 = 14
# Total: 3 + 14 = 17
| Role | Max Complexity | Max Depth | Max Breadth |
|---|---|---|---|
| Anonymous | 50 | 3 | 25 |
| Authenticated | 100 | 5 | 50 |
| Admin | Unlimited | Unlimited | Unlimited |
# Enable complexity limiting (Layer 1)
--enable-complexity-limit=true
# Set limits (in server config)
# These are role-based and configured internally
Prevents deeply nested queries that can cause exponential data loading.
# ✅ Allowed (depth 3)
{
users { # Depth 1
posts { # Depth 2
comments { # Depth 3
id
text
}
}
}
}
# ❌ Rejected (depth 6 exceeds limit)
{
users { # Depth 1
posts { # Depth 2
comments { # Depth 3
author { # Depth 4
posts { # Depth 5
comments { # Depth 6
text
}
}
}
}
}
}
}
# Enable depth limiting (Layer 2)
--enable-depth-limit=true
Limits requests per user/IP to prevent abuse.
SyndrDB uses Token Bucket algorithm:
| Role | Tokens/Minute | Bucket Capacity | Query Cost | DDL Cost |
|---|---|---|---|---|
| Anonymous | 30 | 30 | 1 token | 5 tokens |
| Authenticated | 60 | 60 | 1 token | 3 tokens |
| Admin | Unlimited | N/A | N/A | N/A |
// User starts with 60 tokens
// Query 1 (simple query, 1 token)
{ users { id name } }
// Remaining: 59 tokens
// Query 2 (simple query, 1 token)
{ products { id title } }
// Remaining: 58 tokens
// Mutation 1 (DDL, 3 tokens)
mutation { createBundle(...) }
// Remaining: 55 tokens
// After 1 minute: refill 60 tokens
// Remaining: 60 tokens (capped at capacity)
{
"data": null,
"errors": [
{
"message": "Rate limit exceeded. Please wait before making more requests.",
"extensions": {
"code": "RATE_LIMIT_EXCEEDED",
"retryAfter": 30
}
}
]
}
# Enable GraphQL rate limiting (Layer 3)
--enable-graphql-rate-limit=true
# Choose algorithm: token-bucket or time-bucket
--graphql-rate-algorithm=token-bucket
Limits execution time to prevent long-running queries.
| Role | Max Execution Time |
|---|---|
| Anonymous | 5 seconds |
| Authenticated | 30 seconds |
| Admin | 60 seconds |
{
"data": null,
"errors": [
{
"message": "Query execution timeout exceeded (30s)",
"extensions": {
"code": "QUERY_TIMEOUT",
"executionTime": 30.5
}
}
]
}
# Enable query timeout (Layer 4)
--enable-query-timeout=true
Tracks query metrics for alerting and analysis.
| Metric | Description |
|---|---|
| Query Duration | Execution time in milliseconds |
| Complexity Score | Calculated complexity |
| Query Depth | Nesting depth |
| Token Cost | Rate limit tokens consumed |
| Success/Failure | Query result status |
| User Context | Username, IP, role |
Queries exceeding thresholds trigger warnings:
⚠️ Expensive GraphQL query detected
User: alice@company.com
Duration: 5.2s
Complexity: 85
Operation: query GetUserPosts
# Enable query monitoring (Layer 5)
--enable-query-monitoring=true
✅ DO:
❌ DON'T:
SyndrDB implements DataLoader to batch and cache database requests.
Without batching:
{
users { # Query 1: Get users
id
posts { # Query 2, 3, 4...: Get posts for EACH user
title
}
}
}
# Result: 1 + N queries (1 for users, N for posts)
With batching:
{
users { # Query 1: Get users
id
posts { # Query 2: Get posts for ALL users in one query
title
}
}
}
# Result: 2 queries total
# ❌ Over-fetching
{
users {
id
name
email
phone
address
city
state
zip
country
createdAt
updatedAt
}
}
# ✅ Request only what you need
{
users {
id
name
email
}
}
# ❌ Load all records
{
users {
id
name
}
}
# ✅ Paginate large result sets
{
users(first: 50) {
edges {
node {
id
name
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
# ❌ Fetch all, filter in client
{
users {
id
name
status # Filter status = "active" in JavaScript
}
}
# ✅ Filter in database
{
users(where: { status: { eq: "active" } }) {
id
name
}
}
# ❌ Duplicate field selections
{
activeUsers: users(where: { status: { eq: "active" } }) {
id
name
email
createdAt
}
adminUsers: users(where: { role: { eq: "admin" } }) {
id
name
email
createdAt
}
}
# ✅ DRY with fragments
fragment UserFields on User {
id
name
email
createdAt
}
{
activeUsers: users(where: { status: { eq: "active" } }) {
...UserFields
}
adminUsers: users(where: { role: { eq: "admin" } }) {
...UserFields
}
}
Use introspection to understand query cost:
{
__schema {
queryType {
fields {
name
description
args {
name
type {
name
}
}
}
}
}
}
GraphQL errors follow a standard format:
{
"data": null,
"errors": [
{
"message": "Human-readable error message",
"path": ["fieldPath", "toError"],
"locations": [
{
"line": 2,
"column": 5
}
],
"extensions": {
"code": "ERROR_CODE",
"additionalInfo": "value"
}
}
]
}
| Code | Meaning | Example |
|---|---|---|
| GRAPHQL_PARSE_FAILED | Query syntax error | Missing closing brace |
| GRAPHQL_VALIDATION_FAILED | Schema validation failed | Unknown field |
| INVALID_FIELD | Field doesn't exist | Typo in field name |
| RATE_LIMIT_EXCEEDED | Too many requests | Exceeded tokens |
| QUERY_TIMEOUT | Execution too slow | Query took > 30s |
| COMPLEXITY_EXCEEDED | Query too complex | Complexity > 100 |
| DEPTH_EXCEEDED | Query too deep | Depth > 5 |
| AUTHENTICATION_REQUIRED | Not authenticated | DDL without auth |
| PERMISSION_DENIED | Insufficient permissions | User lacks role |
Query:
{
users {
id
name
# Missing closing brace
}
Response:
{
"errors": [
{
"message": "Syntax Error: Expected Name, found <EOF>",
"locations": [{ "line": 5, "column": 1 }],
"extensions": {
"code": "GRAPHQL_PARSE_FAILED"
}
}
]
}
Query:
{
users {
id
invalidField
}
}
Response:
{
"errors": [
{
"message": "Cannot query field 'invalidField' on type 'User'",
"path": ["users", "invalidField"],
"locations": [{ "line": 4, "column": 5 }],
"extensions": {
"code": "INVALID_FIELD"
}
}
]
}
Response:
{
"data": null,
"errors": [
{
"message": "Rate limit exceeded. Please wait before making more requests.",
"extensions": {
"code": "RATE_LIMIT_EXCEEDED",
"retryAfter": 30,
"availableTokens": 0,
"capacity": 60
}
}
]
}
Response:
{
"data": null,
"errors": [
{
"message": "Query complexity 125 exceeds maximum 100",
"extensions": {
"code": "COMPLEXITY_EXCEEDED",
"complexity": 125,
"maxComplexity": 100,
"depth": 6
}
}
]
}
GraphQL allows partial success:
{
"data": {
"users": [
{ "id": "1", "name": "Alice" },
{ "id": "2", "name": "Bob" }
],
"products": null
},
"errors": [
{
"message": "Bundle 'products' not found",
"path": ["products"],
"extensions": {
"code": "BUNDLE_NOT_FOUND"
}
}
]
}
Interpretation: users query succeeded, products query failed.
✅ DO:
data and errors in response❌ DON'T:
data is always populatedextensions field (contains useful metadata)# Create user
mutation CreateUser {
createUser(input: {
name: "Alice Johnson"
email: "alice@company.com"
role: "data-writer"
age: 28
status: "active"
}) {
id
name
email
createdAt
}
}
# Query active users with pagination
query GetActiveUsers($cursor: String) {
users(
first: 20
after: $cursor
where: { status: { eq: "active" } }
orderBy: [{ field: "createdAt", direction: DESC }]
) {
edges {
node {
id
name
email
role
createdAt
}
cursor
}
pageInfo {
hasNextPage
endCursor
}
totalCount
}
}
# Update user
mutation UpdateUser($id: String!) {
updateUser(id: $id, input: {
status: "inactive"
}) {
id
status
updatedAt
}
}
# Delete user
mutation DeleteUser($id: String!) {
deleteUser(id: $id) {
success
deletedCount
}
}
# Complex product search with filters
query SearchProducts(
$minPrice: Float!
$maxPrice: Float!
$category: String!
$inStock: Boolean!
$cursor: String
) {
products(
first: 24
after: $cursor
where: {
and: [
{ price: { gte: $minPrice } }
{ price: { lte: $maxPrice } }
{ category: { eq: $category } }
{ inStock: { eq: $inStock } }
]
}
orderBy: [
{ field: "price", direction: ASC }
]
) {
edges {
node {
id
title
description
price
category
inStock
imageUrl
}
cursor
}
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
totalCount
}
}
# Variables
{
"minPrice": 10.00,
"maxPrice": 100.00,
"category": "electronics",
"inStock": true,
"cursor": null
}
# Dashboard with multiple queries
query Dashboard {
# Total users
totalUsers: users {
edges { node { id } }
totalCount
}
# Active users
activeUsers: users(where: { status: { eq: "active" } }) {
totalCount
}
# New users this week
newUsers: users(
where: {
createdAt: {
gte: "2024-01-13T00:00:00Z"
}
}
) {
edges {
node {
id
name
email
createdAt
}
}
totalCount
}
# Users by role
admins: users(where: { role: { eq: "admin" } }) {
totalCount
}
writers: users(where: { role: { eq: "data-writer" } }) {
totalCount
}
readers: users(where: { role: { eq: "data-reader" } }) {
totalCount
}
}
# Discover available bundles
{
__schema {
queryType {
fields {
name
description
}
}
}
}
# Get schema for specific bundle
{
__type(name: "User") {
name
description
fields {
name
type {
name
kind
ofType {
name
kind
}
}
description
}
}
}
✅ DO:
❌ DON'T:
✅ DO:
❌ DON'T:
✅ DO:
❌ DON'T:
hasNextPage in pagination✅ DO:
data and errors fields❌ DON'T:
✅ DO:
❌ DON'T:
# Basic query
GRAPHQL::{ "query": "{ users { id name } }" }
# With variables
GRAPHQL::{
"query": "query GetUser($id: String!) { user(id: $id) { name } }",
"variables": { "id": "user_123" }
}
# Mutation
GRAPHQL::{
"query": "mutation CreateUser($input: CreateUserInput!) { createUser(input: $input) { id } }",
"variables": { "input": { "name": "Alice" } }
}
| Pattern | Example |
|---|---|
| Basic Query | { users { id name } } |
| With Filter | { users(where: { status: { eq: "active" } }) { id } } |
| With Pagination | { users(first: 10, after: "cursor") { edges { node { id } } } } |
| Multiple Bundles | { users { id } products { id } } |
| With Alias | { activeUsers: users(where: {...}) { id } } |
| With Fragment | { users { ...UserFields } } fragment UserFields on User { id name } |
| Layer | Anonymous | Authenticated | Admin |
|---|---|---|---|
| Complexity | 50 | 100 | ∞ |
| Depth | 3 | 5 | ∞ |
| Rate (req/min) | 30 | 60 | ∞ |
| Timeout (sec) | 5 | 30 | 60 |
# Enable GraphQL
--graphql=true
# Security layers
--enable-complexity-limit=true # Layer 1
--enable-depth-limit=true # Layer 2
--enable-graphql-rate-limit=true # Layer 3
--enable-query-timeout=true # Layer 4
--enable-query-monitoring=true # Layer 5
# Rate limiting
--graphql-rate-algorithm=token-bucket
End of SyndrDB GraphQL Documentation