Sign inSign up

farisbahdlor/grapthway

By farisbahdlor

β€’Updated about 1 year ago

A declarative API gateway that unifies GraphQL and REST https://github.com/Grapthway/Gateway-Engine

Image
Networking
Languages & frameworks
API management
0

235

farisbahdlor/grapthway repository overview

β πŸš€ Grapthway v2.0

Grapthway Logo

The Future of Microservices Orchestration

A declarative API gateway that unifies GraphQL and REST services with a powerful, protocol-agnostic pipeline engine.

Docker Pulls GitHub Stars License

🎯 Quick Start⁠ β€’ ✨ What's New⁠ β€’ πŸ—οΈ Architecture⁠ β€’ πŸ”§ Examples⁠ β€’ πŸ“– Documentation⁠ β€’ 🀝 Contributing⁠


⁠✨ What's New in v2.0?

Grapthway v2.0 is a major evolution, transforming from a GraphQL-only gateway into a Unified Orchestration Platform for both GraphQL and REST services.

⁠πŸ”₯ Major Features
πŸš€ Unified Pipeline Engine⚑ High-Performance REST Proxy
Same powerful pre/post pipeline steps for both GraphQL fields and REST endpointsREST routes without pipelines handled by direct reverse proxy
Protocol-agnostic workflows with consistent syntaxZero performance overhead for non-orchestrated traffic
Create complex orchestrations spanning multiple service typesSeamless integration with existing REST services
πŸ”§ REST Body Mapping🏒 Enterprise HA Resume
New bodyMapping attribute for declarative JSON request constructionRedis-backed gateways restart and resume previous state
Map data from pipeline context or use static valuesAutomatic token recovery without re-running /start
Available in pre, post, and rollback pipeline stepsEnhanced high-availability deployment patterns
πŸ’ͺ Transactional Rollbacks
onError handlers can trigger compensating rollback steps
Build resilient, transactional workflows
Safely undo actions across multiple services
⁠🎯 Migration from v1.5
  • Backward Compatible: All v1.5 GraphQL configurations work unchanged
  • New Capabilities: Add REST endpoints and pipelines as needed
  • Performance: Existing GraphQL workloads see performance improvements

β πŸ—οΈ Architecture

⁠System Architecture (v2.0)

The v2.0 architecture introduces a unified Pipeline Executor at its core. This central component is responsible for orchestrating workflows for both GraphQL and REST requests, ensuring consistent logic for tasks like authentication, data enrichment, and logging. For REST routes without a defined pipeline, the gateway bypasses the executor and uses a high-performance reverse proxy to minimize latency.

⁠Unified Request Flow
graph TD
    A[Client Request] --> B{GraphQL or REST?};
    B -- GraphQL --> C[GraphQL Handler];
    B -- REST --> D[REST Proxy Handler];

    C --> E{Pipeline Configured?};
    D --> E;
    
    E -- Yes --> F[Unified Pipeline Executor];
    F -- Pre-Pipeline --> G[Proxy to Downstream Service];
    G -- Response --> F;
    F -- Post-Pipeline --> H[Compose Final Response];
    
    E -- No (REST Only) --> I[High-Speed Reverse Proxy];
    I --> G;
    H --> Z[Client Response];
⁠Pipeline Executor Logic
sequenceDiagram
    participant Handler as Request Handler
    participant Executor as Pipeline Executor
    participant Service as Downstream Service
    
    Handler->>Executor: ExecutePrePipeline(config, context)
    Executor->>Service: Execute Step 1 (e.g., Auth)
    Service-->>Executor: Step 1 Response
    Executor->>Executor: Update Context
    Executor->>Service: Execute Step 2 (e.g., Get Data)
    Service-->>Executor: Step 2 Response
    Executor-->>Handler: Return Pipeline Context
    
    Handler->>Service: Main Request
    Service-->>Handler: Main Response

    Handler->>Executor: ExecutePostPipeline(config, mainResponse)
    Executor->>Service: Enrichment Call
    Service-->>Executor: Enrichment Data
    Executor-->>Handler: Merged Final Data
⁠GraphQL Schema Stitching

Schema stitching allows you to extend types defined in one service with fields resolved by another, creating a rich, interconnected data graph. This feature is specific to GraphQL services.

graph TD
    subgraph "Unified Schema (in Gateway)"
        C["type User { id: ID, name: String, orderHistory: [Order] }"]
    end
    
    subgraph "user-service"
        A["type User { id: ID, name: String }"]
    end
    
    subgraph "order-service"
        B["type Query { getOrdersByUserId(userId: ID!): [Order] }"]
    end
    
    C -- Stitched from --> A;
    C -- Extended with field resolved by --> B;

⁠🌟 Key Features

πŸ”₯ Core CapabilitiesπŸ›‘οΈ Production Features
Unified API Gateway for GraphQL and RESTIntelligent Blue-Green Deployments via health checks
Declarative, Protocol-Agnostic PipelinesRedis Persistence for enterprise deployments
Dynamic GraphQL Schema StitchingReal-time Admin Dashboard with live metrics
Transactional Workflows with RollbacksWebSocket Log Streaming for instant debugging
Automatic Service Discovery & Health MonitoringAutomatic Cleanup of stale services
Round-Robin Load BalancingGraphQL Subscriptions over WebSockets
Context Propagation via X-Ctx-* headersHigh-Performance REST Proxy for legacy systems

⁠🎯 Quick Start

⁠🐳 Run with Docker

Choose your edition and get started in seconds with the new v2.0 images:

# πŸ†“ Community Edition (Perfect for development)
docker run -d -p 5000:5000 --name my-community-gateway farisbahdlor/grapthway:community-v2.0

# 🏒 Enterprise Edition (Requires Redis for persistence)
docker run -d -p 5000:5000 \
  -e STORAGE_TYPE=redis \
  -e REDIS_ADDR=your-redis-host:6379 \
  --name my-enterprise-gateway farisbahdlor/grapthway:enterprise-v2.0
β πŸ”‘ Get Your Tokens

On the first run, initialize the gateway to get your admin tokens:

curl http://localhost:5000/start

πŸ’‘ Pro Tip: Store your admin tokens securelyβ€”you'll need them for service registration and dashboard access. Enterprise users with Redis only need to do this once.

⁠πŸŽͺ Register Your First Service

From your microservice, send a POST request to /admin/health every 30 seconds. This single payload can now define all your service's pipelines, for both GraphQL and REST.

⁠GraphQL-Only Service
// Example: A GraphQL-only authentication service
{
  "service": "auth-service", // The unique name of your service.
  "url": "http://auth-internal:4001/graphql", // The internal address of your service.
  "type": "graphql", // The primary type of this service.
  "subgraph": "auth", // The logical group this service's schema belongs to.
  "schema": "type User { ... } type Mutation { login(user: String!): String }", // The GraphQL schema definition.
  
  // Defines pipelines for specific GraphQL fields.
  "middlewareMap": {
    "login": { // This pipeline attaches to the 'login' mutation.
      "pre": [
        {
          "service": "rate-limiter-rest", // Call a REST service before the main mutation.
          "method": "POST",
          "path": "/check",
          "onError": { "stop": true } // If the rate-limiter fails, stop the entire request.
        }
      ]
    }
  },

  // Defines how to "stitch" fields from this service onto types from other services.
  "stitchingConfig": {
    "User": { // Extend the 'User' type.
      "authInfo": { // With a new field called 'authInfo'.
        "service": "auth-service", // This service will provide the data.
        "resolverField": "getUserById", // By calling its 'getUserById' query.
        "argsMapping": { "id": "id" } // Map the parent User's 'id' to the resolver's 'id' argument.
      }
    }
  }
}
⁠REST-Only Service
// Example: A REST-only legacy inventory service
{
  "service": "inventory-api", // The unique name for the REST service.
  "url": "http://inventory-legacy:3000", // The base URL for the service.
  "type": "rest", // The service type is REST.
  "path": "/v1/inventory", // The base path prefix for all routes from this service.
  
  // Defines pipelines for specific REST routes.
  "restPipelines": {
    "POST /v1/inventory/update": { // Attaches to the 'POST' method on this specific route.
      "pre": [
        {
          "service": "auth-service", // A GraphQL service used for auth.
          "field": "verifyAdmin", // Call this GraphQL field.
          "passHeaders": ["Authorization"], // Pass the auth header from the original request.
          "onError": { "stop": true } // If auth fails, stop the request.
        }
      ]
    }
  }
}
⁠Hybrid Service
// Example: A hybrid service with both GraphQL and REST capabilities
{
  "service": "products-service",
  "url": "http://products-service-internal:8000",
  "type": "graphql", // The primary type. REST routes are an addition.
  "subgraph": "products",
  "schema": "type Query { getProduct(id: ID!): Product } type Product { id: ID name: String }",
  
  // Pipelines for GraphQL fields.
  "middlewareMap": {
    "getProduct": {
      "pre": [{
        "service": "auth-service",
        "field": "validateSession",
        "onError": { "stop": true }
      }]
    }
  },

  // Pipelines for REST endpoints also served by this service.
  "restPipelines": {
    "POST /products/{id}/inventory": {
       "pre": [{
        "service": "auth-service",
        "field": "verifyAdmin",
        "onError": { "stop": true }
      }]
    }
  },

  // Schema stitching configuration.
  "stitchingConfig": {
    "Order": { // Extends the 'Order' type (defined in another service).
      "productDetails": { // Adds a 'productDetails' field.
        "service": "products-service", // This service resolves the field.
        "resolverField": "getProduct", // Using its 'getProduct' query.
        "argsMapping": { "id": "productId" } // Maps the parent Order's 'productId' to the 'id' argument.
      }
    }
  }
}

β πŸ—οΈ Advanced Examples

β πŸ›’ E-commerce Order Placement

A user places an order via a GraphQL mutation. The pipeline validates the user (GraphQL), checks stock (REST), and after the main mutation, sends a confirmation email (REST).

// Attached to GraphQL mutation: placeOrder(items: [ItemInput!]): Order
// This mutation is assumed to return an object like: { "id": "ord_123", "buyerId": "usr_456", "total": 99.99 }
{
  "placeOrder": {
    "pre": [
      {
        // 1. First, call the 'auth-service-gql' GraphQL service to validate the user session.
        "service": "auth-service-gql",
        "field": "validateSession",
        "passHeaders": ["Authorization"],
        "selection": ["user { id email }"], // Only ask for the fields we need.
        "assign": { "userContext": "user" }, // Assign the 'user' object from the response to the 'userContext' key in the pipeline context.
        "onError": { "stop": true } // If this fails, stop the entire process.
      },
      {
        // 2. Next, call the 'inventory-api-rest' REST service to check stock.
        "service": "inventory-api-rest",
        "method": "POST",
        "path": "/v1/stock/check-availability",
        "bodyMapping": { 
          // Get the 'items' from the original GraphQL mutation arguments and map them to the request body.
          "items_to_check": "args.items" 
        },
        "onError": { "stop": true }
      }
    ],
    "post": [
      {
        // 3. After the main 'placeOrder' mutation succeeds, call the email REST service.
        "service": "email-api-rest",
        "method": "POST",
        "path": "/v2/send/order-confirmation",
        "bodyMapping": {
            // Get the user's email from the context we saved in the 'pre' step.
            "recipient_email": "userContext.email",
            // Get the 'id' field from the main 'placeOrder' mutation's response.
            "order_id": "id",
             // Get the 'total' field from the main 'placeOrder' mutation's response.
            "order_total": "total"
        },
        "concurrent": true // Run this as fire-and-forget; don't wait for it to complete.
      }
    ]
  }
}
β πŸ“± Social Media Post Creation (REST-first)

A user creates a post via a POST to a REST endpoint. The pipeline validates the session (GraphQL), runs content moderation (REST), and after success, notifies followers (GraphQL).

// Attached to REST endpoint: POST /v3/posts
// The main REST handler is assumed to return a response like: { "id": "post_789", "content": "..." }
{
  "POST /v3/posts": {
    "pre": [
      {
        // 1. Authenticate the user via a GraphQL call.
        "service": "user-session-gql",
        "field": "getActiveUser",
        "passHeaders": ["Authorization"],
        "selection": ["id"],
        "assign": { "user": "" }, // Assign the entire response object (e.g., { "id": "..." }) to the 'user' key.
        "onError": { "stop": true }
      },
      {
        // 2. Run the post content through a moderation REST API.
        "service": "moderation-api-rest",
        "method": "POST",
        "path": "/filter/text",
        "bodyMapping": { 
          // Get the 'content' from the body of the original REST request.
          "text_content": "request.body.content" 
        },
        "onError": { "stop": true }
      }
    ],
    "post": [
      {
        // 3. After the post is created, trigger a GraphQL mutation to notify followers.
        "service": "notification-gql",
        "operation": "mutation",
        "field": "notifyFollowersOfNewPost",
        "argsMapping": { 
            // Map the 'id' from the main REST response to the 'postId' argument.
            "postId": "id", 
            // Map the user's ID from the context (set in the 'pre' step) to the 'authorId' argument.
            "authorId": "user.id"
        },
        "concurrent": true
      }
    ]
  }
}
β πŸ’³ Financial Transaction with Rollback

A pre pipeline step authorizes a payment (REST). A subsequent step to reserve tickets (GraphQL) fails, triggering a rollback that calls the payment provider's void endpoint to cancel the authorization.

// Attached to GraphQL mutation: reserveTickets(showId: ID!, quantity: Int!)
{
  "reserveTickets": {
    "pre": [
       {
        // 1. Authorize payment with a third-party REST API.
        "service": "payment-provider-api",
        "method": "POST",
        "path": "/v1/authorize_payment",
        "bodyMapping": {
            // Use data from the GraphQL arguments.
            "amount": "args.ticketPrice",
            "currency": "USD", // Use a static value.
            "card_token": "args.paymentToken"
        },
        "assign": { "paymentAuth": "" }, // Store the entire auth response (e.g., { "transactionId": "..." }) in the context.
        "onError": { "stop": true }
      },
      {
         // 2. Attempt to reserve tickets. Let's assume this step fails.
         "service": "ticketing-service-gql",
         "field": "createReservation",
         "argsMapping": { "showId": "args.showId" },
         "assign": { "reservation": ""},
         // Because this blocking step failed, the 'rollback' action is triggered.
         "onError": {
            "stop": true,
            "rollback": [
              {
                // This is the compensating action: void the payment authorization.
                "service": "payment-provider-api",
                "method": "POST",
                "path": "/v1/void_authorization",
                "bodyMapping": {
                  // Get the transaction ID from the context we saved in the first step.
                  "authorization_id": "paymentAuth.transactionId",
                  // Provide a static reason code for the void action.
                  "reason_code": "INVENTORY_UNAVAILABLE"
                }
              }
            ]
         }
      }
    ]
  }
}
⁠🌐 IoT Data Ingestion & Alerting

An IoT device sends data to a REST endpoint. A post pipeline checks for anomalies (GraphQL) and, if an alert is found, sends a notification to a Slack webhook (REST).

// Attached to REST endpoint: POST /ingest/telemetry
{
  "POST /ingest/telemetry": {
    "pre": [{
        // 1. Validate the device's token before processing the telemetry data.
        "service": "device-auth-api",
        "method": "POST",
        "path": "/token/introspect",
        "passHeaders": ["X-Device-Token"],
        "assign": { "device": "" }, // Store device info in the context.
        "onError": { "stop": true }
    }],
    "post": [
      {
        // 2. After data ingestion, call a GraphQL service to check for anomalies. This call is blocking.
        "service": "anomaly-detection-gql",
        "field": "analyzeTelemetry",
        "argsMapping": { "payload": "payload" }, // Assumes main handler returns { "payload": ... }
        "selection": ["alert { level message }"],
        "assign": { "anomalyResult": "" }, // Store the analysis result in the context.
        "concurrent": false // 'false' is default, but explicit here. The next step depends on this result.
      },
      {
        // 3. This step runs after the anomaly check. It will send a Slack message.
        // Note: The pipeline doesn't have conditional logic. If no alert is found, this may send an empty message or fail gracefully.
        "service": "slack-webhook-rest",
        "method": "POST",
        "path": "/T012345/B67890/xyz...", // The unique part of the Slack webhook URL.
        "bodyMapping": { 
          "text": "anomalyResult.alert.message" // Get the alert message from the previous step's result.
        },
        "concurrent": true
      }
    ]
  }
}

β πŸ“Š Built-in Observability

β πŸ“ˆ Real-time Dashboard
  • Live service status and health metrics
  • Pipeline execution monitoring for both GraphQL and REST
  • Interactive schema explorer
  • WebSocket log streaming
β πŸ“ Comprehensive Logging
  • Gateway Logs: Request/response cycles with pipeline traces
  • Admin Logs: Service registrations and configuration changes
  • Schema Logs: Complete audit trail of schema evolution
  • REST Logs: HTTP request/response logging with pipeline context
β πŸ” Diagnostic Endpoints
EndpointPurpose
/admin/servicesList all registered services and instances
/admin/schemaView raw SDL schemas
/admin/gateway-statusDetailed gateway metrics
/admin/pipelinesActive pipeline configurations

⁠🏒 Edition Comparison

FeatureπŸ†“ Community🏒 Enterprise
StorageIn-memoryRedis persistence
Services per subgraphUp to 3Unlimited
Max subgraphs2Unlimited
REST endpointsβœ… Full supportβœ… Full support
Pipeline engineβœ… Full supportβœ… Full support
Log retention2 daysConfigurable
HA ResumeβŒβœ…
SupportCommunityPriority + SLA

β πŸ”§ Architecture Deep Dive

sequenceDiagram
    participant C as Client
    participant G as Gateway
    participant M as Pipeline Engine
    participant GS as GraphQL Services
    participant RS as REST Services
    participant D as Dashboard

    C->>G: GraphQL/REST Request
    G->>M: Execute Pre-Pipeline
    M->>GS: Route to GraphQL Service(s)
    M->>RS: Route to REST Service(s)
    GS-->>M: GraphQL Response
    RS-->>M: REST Response
    M->>G: Execute Post-Pipeline  
    G-->>C: Final Response
    G->>D: Stream Logs
β πŸ”„ Request Lifecycle
  1. Pre-processing: Authentication, validation, context injection (both GraphQL and REST)
  2. Service routing: Intelligent load balancing across instances
  3. Response handling: Data enrichment and transformation
  4. Post-processing: Notifications, analytics, cleanup
  5. Observability: Real-time logging and metrics

β πŸ› οΈ Troubleshooting

⁠Common Issues & Solutions
IssueSolution
πŸ”΄ Service not appearingCheck announcement frequency (e.g., every 30s) and network connectivity.
🟑 Schema not updatingValidate schema format and check gateway logs for parsing or merging errors.
🟠 Pre-pipeline not runningEnsure the service is announcing its middlewareMap or restPipelines correctly.
πŸ”΅ Dashboard not loadingConfirm admin token and WebSocket connectivity to the gateway.
🟣 REST endpoint not foundCheck the path configuration and ensure the service type is set correctly.
⁠Debug Commands
# Check gateway status
curl "http://localhost:5000/admin/gateway-status?token=${ADMIN_TOKEN}"

# View active services  
curl "http://localhost:5000/admin/services?token=${ADMIN_TOKEN}"

# Inspect pipeline configurations
curl "http://localhost:5000/admin/pipelines?token=${ADMIN_TOKEN}"

# Test REST endpoint directly
curl "http://localhost:5000/your-rest-path" -X POST

β πŸ“– Documentation

For comprehensive documentation, advanced configurations, and best practices:

πŸ“š Complete Engineering Manual⁠


⁠🀝 Contributing

We welcome contributions from the community! Whether it's bug reports, or feature suggestions, please feel free to:


β πŸ“„ License

Grapthway is available in two editions:

  • Community Edition: Free-to-use with core features
  • Enterprise Edition: Commercial license with advanced features and support

Your use of any Grapthway Docker image is subject to the terms and conditions outlined in our Software License Agreement.

Please read the full agreement here: LICENSE.md⁠


β πŸ™ Acknowledgments

Built with ❀️ by the Grapthway team. Special thanks to:

  • The GraphQL and REST communities for inspiration
  • Early adopters and v2.0 beta testers
  • Contributors and open source maintainers
  • The cloud-native community for continuous feedback

Ready to unify your microservices architecture?

πŸš€ Get Started with v2.0⁠ β€’ πŸ’¬ Join Community⁠ β€’ 🐦 Follow Updates⁠

Made with ❀️ for the cloud-native community

Tag summary

Content type

Image

Digest

sha256:f14328799…

Size

8 MB

Last updated

about 1 year ago

docker pull farisbahdlor/grapthway:community-v2.0