Lightweight API Gateway for REST, GraphQL and legacy Web Services, easily extended with powerful plugins.
Based on the Java platform, Membrane integrates smoothly with major enterprise technologies. Load tests show that Java provides an excellent foundation for high performance and scalability. On a 2021 MacBook Pro, Membrane handles more than 20,000 requests per second, supports up to 10,000 concurrent clients, and can host over 100,000 APIs on a single instance.
The examples below demonstrate how to address a wide range of API Gateway requirements using simple configurations. Version 7.0.0 or newer is required.
Forwarding Requests from Port 2000 to a Backend:
api: port: 2000 target: url: https://api.predic8.de
Path Rewriting with an URI Template:
api: port: 2000 path: uri: /fruit/{id} target: url: https://api.predic8.de/shop/v2/products/${pathParam.id}
Deploy OpenAPI and enable Request Validation:
api: port: 2000 openapi: - location: "fruitshop-api.yml" validateRequests: yes
Issue JSON Web Tokens:
To issue a JWT for a user, create an API that acts as a simple token endpoint:
api: port: 2000 path: uri: /token flow: - basicAuthentication: htpasswdFileProvider: location: .htpasswd - request: - template: contentType: application/json src: | { "sub": ${user()} } - jwtSign: jwk: location: jwk.json - return: status: 200
Authenticated requests to '/token' return a signed JWT in which the username from Basic Authentication is used as the sub claim.
{
"typ": "JWT",
"alg": "RS256"
}
.
{
"sub": "alice",
"iat": 1765222877,
"exp": 1765223177
}
.hTL_0-AS8IZgiDUJ6Kg...
This example is intentionally minimal, but it highlights the basic building blocks: authenticate the caller, shape the token payload, and sign the result. From there, you can extend it with additional claims, custom logic, or stricter policies to implement tailored API security flows.
API Gateway eBook
Learn how API Gateways work with real-world examples and insights into Membrane.
Download instantly — no registration required.
Features
OpenAPI
- Deploy APIs from OpenAPI specifications.
- Validate requests and responses against OpenAPI and JSON Schema.
API Security
- JSON Web Tokens, OAuth2, API Keys, NTLM, and Basic Authentication.
- Built-in OAuth2 Authorization Server.
- Rate limiting and traffic control
- Protection for GraphQL, JSON, and XML APIs against malicious inputs.
Legacy Web Services
- Seamless support for SOAP message routing.
- Configure, validate, and rewrite WSDL-based services, including message validation.
Additional Features
- Admin Web Console for monitoring and management.
- Advanced load balancing to ensure high availability.
- Flexible message transformation for seamless data processing.
- Embeddable reverse proxy HTTP framework to build custom API gateways.
- Traffic shadowing
Speed & Size
- Streams HTTP traffic for low-latency, non-blocking processing.
- Reuses TCP connections via HTTP Keep-Alive to reduce request overhead.
- Lightweight distribution (~55MB) compared to other Java-based gateways.
- Low memory footprint, ideal for containers and cloud-native environments.
- Java-based, yet competitive with C/C++ gateways in performance.
Content
- Getting Started
- Basics Routing, rewriting
- OpenAPI Support
- Routing
- Scripting
- Message Transformation
- Conditionals with if
- Security
- Traffic Control Rate limiting, Load balancing
- Legacy Web Services SOAP and WSDL
- Operation
- Community and professional Support
Installation
You can run Membrane as Docker container, standalone Java application or install it on Linux as RPM.
Java
-
Download and extract
- Download a release and unzip it.
-
Start the Gateway
- Open a terminal in the extracted folder.
- Make sure Java 21 or newer is installed:
- Start:
- Linux/Mac:
./membrane.sh - Windows:
membrane.cmd
- Linux/Mac:
-
Access the Gateway
-
Change the Configuration
Modify the preconfigured APIs or add APIs by editing the
proxies.xmlfile in theconffolder.
Docker
-
Start a Membrane container
docker run -p 2000:2000 predic8/membrane
-
Access the Gateway
Test an API by opening http://localhost:2000.
-
Change the Configuration
-
Download proxies.xml or:
wget https://raw.githubusercontent.com/membrane/api-gateway/master/distribution/router/conf/proxies.xml
-
Bind the configuration file to the container.
Mac/Linux:
docker run -v "$(pwd)/proxies.xml:/opt/membrane/conf/proxies.xml" -p 2000:2000 predic8/membraneWindows:
docker run -v %cd%\proxies.xml:/opt/membrane/conf/proxies.xml -p 2000:2000 predic8/membraneYou can now edit
proxies.xmland restart the container to apply the changes.
-
For detailed Docker setup instructions, see the Membrane Deployment Guide.
Getting Started
Explore and Experiment
- Try the code snippets on this page.
- Run the samples in the examples folder of the distribution.
Dive into Tutorials
- Follow the REST API Tutorial to learn about deploying and securing APIs.
- Check out the SOAP API Tutorial for legacy web service integration.
Documentation
- Read the API Gateway eBook
- Look at the documentation.
- Browse the reference
- Try the recipes from the cookbook
Basics
API Definition and Configuration
To define new APIs or modify the existing configuration, edit the proxies.xml file located in the conf folder. This file serves as the central configuration point for managing API behavior and routing rules.
Using Samples
Explore and copy the sample snippets below into the proxies.xml file and modify them to suit your needs. Then save or restart the gateway to apply the changes. Usually a save will trigger a reload automatically.
For even more samples have a look at the examples folder.
Simple REST and HTTP Forwarding APIs
Define an API Route
To forward requests from the API Gateway to a backend, use a simple api configuration. The example below routes requests received on port 2000 with a path starting with /shop to the backend at https://api.predic8.de:
api: port: 2000 path: uri: /shop target: url: https://api.predic8.de
Testing the Configuration
After modifying and saving the proxies.xml file, open http://localhost:2000/shop/v2/
OpenAPI Support
Membrane natively supports OpenAPI, allowing you to easily configure the gateway with OpenAPI documents and automatically validate both requests and responses.
Deploy APIs with OpenAPI
Membrane allows you to configure APIs directly from OpenAPI documents in the proxies.xml file. Backend addresses and other details are automatically derived from the OpenAPI description.
Example Configuration
The snippet below shows how to deploy an API using an OpenAPI (openapi/fruitshop-v2-2-0.oas.yml) with request validation enabled:
api: port: 2000 openapi: - location: openapi/fruitshop-v2-2-0.oas.yml validateRequests: true
Viewing Deployed APIs
Once configured, a list of deployed APIs is available at: http://localhost:2000/api-docs
Click on an API title in the list to open the Swagger UI for interactive exploration and testing:
Learn More
For additional details and a working example, check out the OpenAPI Example.
Routing
Membrane provides versatile routing with a fallthrough mechanism that applies only the first matching API rule, ensuring precise and efficient routing based on path, HTTP method, or hostname or many other criterias.
Example: Advanced Routing
The configuration below demonstrates several routing rules:
# POST requests api: port: 2000 method: POST flow: - response: - static: src: POST is blocked! - return: status: 405 --- # Regex path matching api: port: 2000 path: uri: /shop/v2/products/.* isRegExp: true target: url: https://api.predic8.de --- # Requests whose HOST header is "www.predic8.de" api: port: 2000 host: www.predic8.de flow: - response: - static: src: "<html>Homepage</html>" - return: status: 200 --- # Requests with a query parameter city and value Paris api: port: 2000 test: params.city?.[0] == 'Paris' flow: - response: - static: src: Oui! - return: status: 200
Configuration Options
| Option | Description |
|---|---|
port |
port Membrane listens for incoming connections. |
method |
- HTTP method (e.g., GET, POST, DELETE).- * matches any method. |
host |
- Hostname e.g. api.predic8.de- Supports basic globbing with * |
test |
- Custom script e.g. $pathParam.id == '42', $header.contentType == '...' |
path |
- Request path - Regular expressions can be used with isRegExp="true" |
For more routing options, see the Membrane API documentation.
Short Circuit
Membrane lets you create endpoints that return immediately without forwarding requests to a backend.
Example: Health Check
The following configuration creates a health check endpoint that responds to requests at http://localhost:2000/health:
api: port: 2000 path: uri: /health flow: - response: - static: src: I'm good. - return: status: 200
Example: Blocking Specific Paths
Block paths (e.g., /nothing) while allowing other calls to pass through.
Routing Note: APIs are matched from top to bottom. When multiple APIs share the same port, place the APIs with stricter routing conditions higher in the configuration.
api: port: 2000 path: uri: /nothing flow: - response: - static: src: "Nothing to see here!" - return: status: 404 --- api: port: 2000 flow: - static: src: Other calls - return: status: 200
URL Rewriting
The URLs of request can be rewritten dynamically before forwarding them to the backend. This is useful for restructuring API paths or managing legacy endpoints.
Example
The following configuration rewrites requests starting with /fruitshop to /shop/v2, preserving the remainder of the path:
api: port: 2000 flow: - rewriter: - from: ^/fruitshop/(.*) to: /shop/v2/$1 target: url: https://api.predic8.de
Testing
A request to:
http://localhost:2000/fruitshop/products/4
will be rewritten to and forwarded to the backend at:
https://api.predic8.de/shop/v2/products/4
Scripting
Membrane has powerful scripting features that allow to modify the desired of an API using Groovy or Javascript.
Use Cases
- Custom Responses: Tailor responses dynamically based on client requests or internal logic.
- Mocking APIs: Simulate API behavior during testing or development phases.
- Dynamic Headers: Add headers conditionally based on business rules.
- Debugging: Inspect incoming requests during development.
Groovy Scripts
The following API executes a Groovy script during the request and the response.
api: port: 2000 flow: - groovy: src: | println "I'm executed in the ${flow} flow" println "HTTP Headers:\n${header}" target: url: https://api.predic8.de
After invoking http://localhost:2000 you can see the following output in the console where you have started Membrane:
I'm executed in the REQUEST flow
HTTP Headers:
Host: localhost:2000
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:133.0) Gecko/20100101 Firefox/133.0
...
I'm executed in the RESPONSE flow
HTTP Headers:
Content-Length: 390
Content-Type: application/json
Dynamically Route to random Target
You can realize a load balancer by setting the destination randomly.
api: port: 2013 flow: - groovy: src: | sites = ["https://api.predic8.de","https://membrane-api.io","https://predic8.de"] Collections.shuffle sites exchange.destinations = sites target: {} # No details needed target uses destinations from exchange
Creating Responses with Groovy
The groovy plugin in Membrane allows you to dynamically generate custom responses. The result of the last line of the Groovy script is passed to the plugin. If the result is a Response object, it will be returned to the caller.
Example
The following example creates a custom JSON response with a status code of 200, a specific content type, and a custom header:
api: port: 2000 flow: - groovy: src: | Response.ok() .contentType("application/json") .header("X-Foo", "bar") .body(""" { "success": true }""") .build()
How It Works
- The
Response.ok()method initializes a new HTTP response with a status of200 OK. - The
contentType()method sets theContent-Typeheader, ensuring the response is identified as JSON. - The
header()method adds custom headers to the response. - The
body()method specifies the response payload. - The
build()method finalizes the response object, which is then returned by thegroovyplugin.
Resulting Response
When accessing this API, the response will look like this:
HTTP/1.1 200 OK
Content-Type: application/json
X-Foo: bar
{
"success": true
}
Learn More about the Groovy Plugin
For more information about using Groovy with Membrane, refer to:
JavaScript Scripts
In addition to Groovy, Membrane supports JavaScript for implementing custom behavior. This allows you to inspect, modify, or log details about requests and responses.
Example
The following example logs all HTTP headers from incoming requests and responses to the console:
api: port: 2000 flow: - javascript: src: | console.log("------------ Headers: -------------"); var fields = header.getAllHeaderFields(); for (var i = 0; i < fields.length; i++) { console.log(fields[i]); } target: url: https://api.predic8.de
The CONTINUE keyword ensures that the request continues processing and is forwarded to the target URL.
When a JavaScript script returns a Response object as the last line of code, the request flow is interrupted, and the response is sent back to the client. This allows for creating custom responses dynamically.
The following example generates a JSON response and sends it directly to the client:
api: port: 2000 flow: - javascript: src: | var body = JSON.stringify({ foo: 7, bar: 42 }); Response.ok(body).contentType("application/json").build();
Learn More
For more details about using JavaScript with Membrane, check the JavaScript Plugin documentation.
Message Transformation
Manipulating HTTP Headers
You can modify HTTP headers in requests or responses using Membrane's setHeader and headerFilter feature. This is particularly useful for enabling CORS or adding custom headers.
Example: Adding CORS Headers
The following configuration adds CORS headers to the responses received from the backend:
api: port: 2000 flow: - response: - setHeader: name: Access-Control-Allow-Origin value: "*" - setHeader: name: Access-Control-Allow-Methods value: GET target: url: https://api.predic8.de
Example: Setting Headers from JSON Body Content
Membrane allows dynamic extraction of values from the JSON body of a request or response and uses them to set HTTP headers.
Example Configuration
The following example extracts the id and name fields from a JSON body and sets them as custom headers in the response:
api: port: 2000 flow: - response: - setHeader: name: X-Product-Id value: ${jsonPath('$.id')} language: spel - setHeader: name: X-Product-Name value: ${$.name} language: jsonpath target: url: https://api.predic8.de
Removing HTTP Headers
You can easily remove specific HTTP headers from requests or responses (or both) using the headerFilter element. This is useful for cleaning up headers or meeting security requirements.
Example: Header Filtering
The following configuration demonstrates how to manage headers:
api: port: 2000 flow: - response: - headerFilter: - include: "X-XSS-Protection" - exclude: "X-.*" target: url: https://www.predic8.de
<include>: Specifies headers to retain.<exclude>: Defines headers to remove. Wildcards can be used for patterns.
The first matching rule will be acted upon by the filter.
Create JSON from Query Parameters
api: port: 2000 flow: - request: - template: contentType: application/json pretty: true src: | { "answer": ${params.answer?[0]} } - return: status: 200
Call this API with http://localhost:2000?answer=42.
Transform JSON into TEXT, JSON or XML with Templates
Call the following APIs with this request:
curl -d '{"city":"Berlin"}' -H "Content-Type: application/json" "http://localhost:2000"
This template will transform the JSON input into plain text:
api: port: 2000 flow: - request: - template: contentType: text/plain src: | City: ${json.city} - return: status: 200
...into a different JSON:
api: port: 2000 flow: - request: - template: contentType: application/json src: | { "destination": ${json.city} } - return: status: 200
...or into XML:
api: port: 2000 flow: - request: - template: contentType: application/xml src: | <places> <place>${json.city}</place> </places> - return: status: 200
Transform XML into Text or JSON
You can use XPath to extract values from an XML message and insert them into a template.
api: port: 2000 flow: - request: - template: src: | Buenas noches, ${xpath('/person/@firstname')} - return: status: 200
See: message-transformation examples
Complex Transformations using Javascript or Groovy
Use the Javascript or Groovy plugin for more powerful yet simple transformations.
api: port: 2000 flow: - request: - javascript: src: | ({ id:7, place: json.city }) - return: status: 200 contentType: application/json
Call the API with this curl command:
curl -d '{"city":"Berlin"}' -H "Content-Type: application/json" "http://localhost:2000"
Transformation with Computations
This script transforms the input and adds some calculations.
api: port: 2000 flow: - request: - javascript: src: | function convertDate(d) { return d.getFullYear() + "-" + ("0"+(d.getMonth()+1)).slice(-2) + "-" + ("0"+d.getDate()).slice(-2); } ({ id: json.id, date: convertDate(new Date(json.date)), client: json.customer, total: json.items.map(i => i.quantity * i.price).reduce((a,b) => a+b), positions: json.items.map(i => ({ pieces: i.quantity, price: i.price, article: i.description })) }) - return: status: 200
See examples/javascript for a detailed explanation. The same transformation can also be realized with Groovy
JSON and XML Beautifier
Use the beautifier to pretty print JSON or XML.
api: port: 2000 flow: - response: - beautifier: {} - template: contentType: application/json src: | { "foo": { "bar": { "baz": 99 }}} - return: status: 200
Result:
{
"foo" : {
"bar" : {
"baz" : 99
}
}
}Conditionals with if
This example shows how to intercept error responses from a backend and replace them with a custom response.
api: port: 2000 flow: - response: - if: test: statusCode >= 500 language: spel flow: - static: src: Failure! target: url: https://httpbin.org/status/500
Security
Membrane offers all kinds of security features to protect APIs and backend servers.
API Keys
API keys can be defined in the configuration, loaded from a file, or stored in a database. Requests are authenticated by validating the provided API key against these sources.
You can also define permissions using scopes in OpenAPI and enforce them with API keys, OAuth 2.0, or JWT-based authentication.
This configuration secures all APIs globally. Alternatively, API keys can be defined for individual APIs only.
global: - apiKey: stores: - simple: - secret: value: aed8bcc4-7c83-44d5-8789-21e4024ac873 - secret: value: 08f121fa-3cda-49c6-90db-1f189ff80756 extractors: - header: X-Api-Key
Advanced Use Cases
More advanced scenarios are supported, including:
- API keys in headers, query parameters or at any other location using expressions.
- Role-based access control (RBAC) with fine-grained permissions.
- OpenAPI-defined permissions.
See the API Key Plugin Examples for detailed configurations.
JSON Web Tokens
The API below only allows requests that present a valid JSON Web Token issued by Microsoft Azure Entra ID. The JWT validator can also be used with other identity providers.
api: port: 2000 flow: - jwtAuth: expectedAud: api://2axxxx16-xxxx-xxxx-xxxx-faxxxxxxxxf0 jwks: jwksUris: https://login.microsoftonline.com/common/discovery/keys target: url: https://your-backend
OAuth2
Secure APIs with OAuth2
Use OAuth2/OpenID to secure endpoints against Google, Azure Entra ID, GitHub, Keycloak or Membrane Authentication Servers.
api: port: 2000 flow: - oauth2Resource2: membrane: src: http://localhost:8000 clientId: abc clientSecret: def scope: openid profile claims: username claimsIdt: sub - request: # Forward the authenticated user’s email to the backend in an HTTP header. - setHeader: name: X-EMAIL value: ${property['membrane.oauth2'].userinfo['email']} target: url: http://backend
Try the tutorial OAuth2 with external OpenID Providers
Membrane as Authorization Server
Membrane includes a fully functional OAuth 2.0 Authorization Server and can also act as an OpenID Connect Provider.
The following example shows a minimal configuration for running Membrane as an OAuth 2.0 authorization server with a static client definition and basic claim and scope setup.
api: port: 8000 flow: - oauth2authserver: issuer: http://localhost:8000 location: logindialog consentFile: consentFile.json staticUserDataProvider: users: - username: john password: secret email: john@predic8.de staticClientList: clients: - clientId: abc clientSecret: def callbackUrl: http://localhost:2000/oauth2callback bearerToken: {} claims: value: aud email iss sub username scopes: - id: username claims: username - id: profile claims: username email
User accounts can be stored directly in the configuration, loaded from a file, or backed by a database.
For a full walkthrough of the authorization code flow, see the OAuth2 Authorization Server example OAuth2 Authorization Server.
Basic Authentication
Sometimes the old basic authentication is enough to provide basic security.
api: port: 2000 flow: - basicAuthentication: users: - username: alice password: secret - username: bob password: secret target: url: https://api.predic8.de
SSL/TLS
TLS is the base for secure API communication.
The first example shows TLS being used for connections from the API Gateway to the backend:
api: port: 2000 # Note the 's' in https! target: url: https://api.predic8.de
The next example secures the public endpoint, enabling TLS for connections from clients to the API Gateway:
api: port: 443 ssl: keystore: location: keystore.p12 password: changeit truststore: location: keystore.p12 password: changeit target: url: http://backend
See more TLS/SSL configuration examples
XML and JSON Protection
Membrane offers protection mechanisms to secure your APIs from common risks associated with XML and JSON payloads.
XML Protection
xmlProtection inspects incoming XML and reduces common attack vectors, including:
- External entity references (XXE).
- Overly long element names.
- High numbers of attributes or deeply nested structures.
api: port: 2000 flow: - xmlProtection: maxAttributeCount: 3 maxElementNameLength: 100 removeDTD: true - return: status: 200
JSON Protection
The jsonProtection plugin safeguards APIs from JSON-based vulnerabilities by setting limits on:
- Depth: Prevents overly nested JSON structures.
- Key Length: Restricts excessively long keys.
- Object Size: Maximum number of fields in a JSON object.
- String Length: Controls maximum length of string values.
- ...
Example:
global: - jsonProtection: maxDepth: 3 maxObjectSize: 50 maxArraySize: 1000
See JSON Protection.
Traffic Control
Rate Limiting
Limit the number of incoming requests:
global: - rateLimiter: requestLimit: 1000 requestLimitDuration: PT1H
Load balancing
Distribute workload to multiple backend nodes. See the example
api: port: 8080 flow: - balancer: clusters: - name: Default nodes: - host: my.backend-1 port: 4000 - host: my.backend-2 port: 4000 - host: my.backend-3 port: 4000
Websockets
Route and intercept WebSocket traffic:
api: port: 2000 flow: - webSocket: url: http://my.websocket.server:1234 flow: - wsLog: {} target: host: localhost port: 8080
See documentation
SOAP Web Services
Integrate legacy services.
API configuration from WSDL
Membrane reads the WSDL and automatically generates a SOAP proxy.
soapProxy: port: 2000 wsdl: https://www.predic8.de/city-service?wsdl
After startup, Membrane exposes:
- A SOAP endpoint at http://localhost:2000/city-service
- A WSDL at http://localhost:2000/city-service?wsdl
Message Validation against WSDL and XSD
The validator checks SOAP messages against a WSDL document including referenced XSD schemas.
soapProxy: port: 2000 wsdl: https://www.predic8.de/city-service?wsdl flow: # Validates SOAP messages against the WSDL and XSDs - validator: {}
Operation
Log HTTP
Log data about requests and responses to a file or database as CSV or JSON file.
api: port: 2000 flow: - log: {} # Logs to the console - statisticsCSV: file: ./log.csv # Logs fine-grained CSV target: url: https://api.predic8.de
Instrumentation
Monitoring with Prometheus and Grafana
This API will expose metrics for Prometheus at http://localhost:2000/metrics:
api: port: 2000 path: uri: /metrics flow: - prometheus: {}
Grafana dashboard from Membrane metrics.
See Prometheus and Grafana example.
OpenTelemetry Integration
Membrane supports integration with OpenTelemetry traces using the openTelemetry plugin and the W3C propagation standard. This enables detailed tracing of requests across Membrane and backend services.

This diagram illustrates Membrane in a tracing setup with a backend service and a database connection.
Example Setup
The configuration below shows Membrane forwarding requests to a backend, while exporting OpenTelemetry data to a collector:
api: port: 2000 flow: - openTelemetry: sampleRate: 1.0 otlpExporter: host: localhost port: 4317 target: host: localhost port: 3000
For a working example and detailed setup, see the OpenTelemetry Example.
Support
Community Support
To get support from our community, please post your questions to our Discussions page @GitHub.
If you find a bug, please report it using GitHub Issues. Please provide a minimal example that reproduces the issue and the version of Membrane you are using.



