Chapter 7: API Development
Overview
This chapter covers the hands-on process of creating, publishing, and consuming APIs using WSO2 API Manager. Includes REST, SOAP-to-REST, GraphQL, and WebSocket APIs with real examples.
Creating a REST API
From the Publisher Portal
1. Login: https://localhost:9443/publisher
2. Create API → REST API → Start From Scratch
3. Fill details:
Name: EmployeeAPI
Context: /employees
Version: 1.0.0
Endpoint: https://backend.example.com/api/employees
4. Define resources → Publish
From OpenAPI Spec
# Import via Publisher UI
Create API → REST API → Import Open API
# Or via apictl
apictl init EmployeeAPI --oas employee-api.yaml
apictl import api -f EmployeeAPI -e production
OpenAPI Definition:
openapi: 3.0.0
info:
title: Employee API
version: 1.0.0
description: Manage employee records
paths:
/employees:
get:
summary: List all employees
parameters:
- name: department
in: query
schema:
type: string
responses:
'200':
description: Employee list
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Employee'
post:
summary: Create employee
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Employee'
responses:
'201':
description: Employee created
/employees/{id}:
get:
summary: Get employee by ID
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
'200':
description: Employee details
put:
summary: Update employee
parameters:
- name: id
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Employee'
responses:
'200':
description: Employee updated
delete:
summary: Delete employee
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
'204':
description: Employee deleted
components:
schemas:
Employee:
type: object
required: [name, email]
properties:
id:
type: string
name:
type: string
email:
type: string
format: email
department:
type: string
role:
type: string
Using the REST API (Publisher)
# Create API via REST endpoint
curl -k -X POST https://localhost:9443/api/am/publisher/v4/apis \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"name": "EmployeeAPI",
"version": "1.0.0",
"context": "/employees",
"endpointConfig": {
"endpoint_type": "http",
"production_endpoints": {
"url": "https://backend.example.com/api/employees"
},
"sandbox_endpoints": {
"url": "https://sandbox.example.com/api/employees"
}
},
"policies": ["Unlimited"],
"operations": [
{"target": "/employees", "verb": "GET", "throttlingPolicy": "Unlimited"},
{"target": "/employees", "verb": "POST", "throttlingPolicy": "Gold"},
{"target": "/employees/{id}", "verb": "GET", "throttlingPolicy": "Unlimited"},
{"target": "/employees/{id}", "verb": "PUT", "throttlingPolicy": "Gold"},
{"target": "/employees/{id}", "verb": "DELETE", "throttlingPolicy": "Gold"}
]
}'
API Endpoint Configuration
Endpoint Types
| Type | Description | Use Case |
|---|---|---|
| HTTP | Standard REST backend | Most common |
| Address | SOAP/generic endpoint | Legacy backends |
| Failover | Primary + backup endpoints | High availability |
| Load Balance | Distribute across backends | Scalability |
| Dynamic | Resolved at runtime | Multi-tenant backends |
Failover Endpoint
{
"endpoint_type": "failover",
"production_endpoints": {
"url": "https://primary.backend.com/api"
},
"production_failovers": [
{"url": "https://secondary.backend.com/api"}
],
"sandbox_endpoints": {
"url": "https://sandbox.backend.com/api"
}
}
Load-Balanced Endpoint
{
"endpoint_type": "load_balance",
"algoClassName": "org.apache.synapse.endpoints.algorithms.RoundRobin",
"production_endpoints": [
{"url": "https://backend1.example.com/api"},
{"url": "https://backend2.example.com/api"},
{"url": "https://backend3.example.com/api"}
]
}
Endpoint Security
{
"endpoint_security": {
"production": {
"enabled": true,
"type": "BASIC",
"username": "backend_user",
"password": "backend_pass"
}
}
}
Supported backend auth types:
BASIC: HTTP Basic AuthenticationDIGEST: HTTP Digest AuthenticationOAUTH: OAuth 2.0 token forwarding
SOAP-to-REST Conversion
Expose legacy SOAP services as modern REST APIs.
Step-by-Step
1. Publisher → Create API → REST API → Import WSDL
2. Provide WSDL URL: http://legacy.example.com/service?wsdl
3. Map SOAP operations to REST resources:
getEmployee → GET /employees/{id}
addEmployee → POST /employees
updateEmployee → PUT /employees/{id}
deleteEmployee → DELETE /employees/{id}
4. Configure mediation for request/response transformation
5. Publish
Mediation Policy for SOAP Backend
In Sequence (REST → SOAP):
<sequence name="rest-to-soap-in" xmlns="http://ws.apache.org/ns/synapse">
<property name="employeeId" expression="get-property('uri.var.id')"/>
<payloadFactory media-type="xml">
<format>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:emp="http://example.com/employee">
<soap:Body>
<emp:getEmployee>
<emp:id>$1</emp:id>
</emp:getEmployee>
</soap:Body>
</soap:Envelope>
</format>
<args>
<arg expression="$ctx:employeeId"/>
</args>
</payloadFactory>
<property name="messageType" value="text/xml" scope="axis2"/>
<header name="SOAPAction" value="getEmployee"/>
</sequence>
Out Sequence (SOAP → REST JSON):
<sequence name="rest-to-soap-out" xmlns="http://ws.apache.org/ns/synapse">
<payloadFactory media-type="json">
<format>
{
"id": "$1",
"name": "$2",
"email": "$3",
"department": "$4"
}
</format>
<args>
<arg expression="//emp:id" xmlns:emp="http://example.com/employee"/>
<arg expression="//emp:name" xmlns:emp="http://example.com/employee"/>
<arg expression="//emp:email" xmlns:emp="http://example.com/employee"/>
<arg expression="//emp:department" xmlns:emp="http://example.com/employee"/>
</args>
</payloadFactory>
<property name="messageType" value="application/json" scope="axis2"/>
</sequence>
GraphQL API
Creating a GraphQL API
1. Publisher → Create API → GraphQL
2. Import schema file (schema.graphql)
3. Map operations to scopes and throttling policies
4. Configure backend endpoint (GraphQL server URL)
5. Publish
Schema Example:
type Query {
employee(id: ID!): Employee
employees(department: String): [Employee]
}
type Mutation {
createEmployee(input: EmployeeInput!): Employee
updateEmployee(id: ID!, input: EmployeeInput!): Employee
deleteEmployee(id: ID!): Boolean
}
input EmployeeInput {
name: String!
email: String!
department: String
}
type Employee {
id: ID!
name: String!
email: String!
department: String
manager: Employee
}
Querying through the gateway:
curl -X POST https://localhost:8243/employees/1.0.0 \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"query": "{ employees(department: \"Engineering\") { id name email } }"
}'
Per-operation throttling:
| Operation | Throttling Policy |
|---|---|
| Query.employees | Unlimited |
| Query.employee | Gold |
| Mutation.createEmployee | Silver |
| Mutation.deleteEmployee | Bronze |
WebSocket API
Creating a WebSocket API
Publisher → Create API → WebSocket API
Name: NotificationAPI
Context: /notifications
Version: 1.0.0
Endpoint: ws://backend.example.com:8080/ws
Client Connection:
const ws = new WebSocket(
'wss://gateway:8243/notifications/1.0.0',
[],
{ headers: { 'Authorization': 'Bearer <token>' } }
);
ws.onopen = () => {
ws.send(JSON.stringify({ subscribe: 'orders' }));
};
ws.onmessage = (event) => {
console.log('Notification:', JSON.parse(event.data));
};
API Versioning
Strategies
| Strategy | Example | When to Use |
|---|---|---|
| URL path | /v1/employees | Most common, clear |
| Query param | /employees?v=1 | Backwards compatible |
| Header | Accept-Version: 1 | Clean URLs |
Creating a New Version
# Via apictl
apictl copy api -n EmployeeAPI -v 1.0.0 -t 2.0.0 -e production
# Or via Publisher REST API
curl -X POST "https://localhost:9443/api/am/publisher/v4/apis/copy-api?newVersion=2.0.0" \
-H "Authorization: Bearer <token>" \
-d '{"apiId": "<API_UUID>"}'
Version Management Best Practices:
- Never break existing consumers silently
- Deprecate old versions with a grace period (3-6 months minimum)
- Communicate changes via Developer Portal changelogs
- Use semantic versioning: MAJOR.MINOR.PATCH
API Mediation Policies
Request Policies
Modify requests before they reach the backend.
<!-- Add header -->
<sequence name="addCorrelationId" xmlns="http://ws.apache.org/ns/synapse">
<property name="X-Correlation-ID"
expression="fn:concat('CID-', get-property('SYSTEM_TIME'))"
scope="transport"/>
</sequence>
Response Policies
Modify responses before returning to client.
<!-- Remove internal headers -->
<sequence name="cleanResponse" xmlns="http://ws.apache.org/ns/synapse">
<property name="X-Internal-Server" scope="transport" action="remove"/>
<property name="X-Debug-Info" scope="transport" action="remove"/>
<header name="Server" action="remove" scope="transport"/>
</sequence>
Fault Policies
Handle backend errors gracefully.
<sequence name="apiFaultHandler" xmlns="http://ws.apache.org/ns/synapse">
<log level="custom">
<property name="fault" value="Backend error"/>
<property name="code" expression="get-property('ERROR_CODE')"/>
</log>
<payloadFactory media-type="json">
<format>
{
"error": {
"code": "$1",
"message": "Service temporarily unavailable",
"reference": "$2"
}
}
</format>
<args>
<arg expression="get-property('ERROR_CODE')"/>
<arg expression="get-property('SYSTEM_TIME')"/>
</args>
</payloadFactory>
<property name="HTTP_SC" value="503" scope="axis2"/>
<respond/>
</sequence>
API Products
Bundle multiple APIs into a single product for simplified consumption.
API Product: HR Suite
├── EmployeeAPI v1.0.0
│ ├── GET /employees
│ └── GET /employees/{id}
├── PayrollAPI v2.0.0
│ └── GET /payroll/{employeeId}
└── LeaveAPI v1.0.0
├── GET /leave/{employeeId}
└── POST /leave
Creating via Publisher:
Publisher → API Products → Create
Name: HRSuite
APIs: EmployeeAPI, PayrollAPI, LeaveAPI
Select specific resources from each API
Publish
Consumers subscribe to the API Product once and get access to all included resources with a single token.
Subscription and Consumption
Developer Workflow
# 1. Create application
curl -X POST https://localhost:9443/api/am/devportal/v3/applications \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"name": "MyWebApp", "throttlingPolicy": "Unlimited"}'
# 2. Subscribe to API
curl -X POST https://localhost:9443/api/am/devportal/v3/subscriptions \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"apiId": "<API_UUID>",
"applicationId": "<APP_UUID>",
"throttlingPolicy": "Gold"
}'
# 3. Generate keys
curl -X POST "https://localhost:9443/api/am/devportal/v3/applications/<APP_UUID>/generate-keys" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"keyType": "PRODUCTION",
"grantTypesToBeSupported": ["client_credentials", "authorization_code"],
"callbackUrl": "https://myapp.com/callback"
}'
# 4. Get access token
curl -X POST https://localhost:8243/token \
-H "Authorization: Basic <base64(key:secret)>" \
-d "grant_type=client_credentials"
# 5. Call API
curl https://localhost:8243/employees/1.0.0/employees \
-H "Authorization: Bearer <access_token>"
API Testing
Built-in Try-It Console
The Developer Portal includes a Swagger-based try-it tool:
Developer Portal → Select API → Try Out → Authorize → Execute
Automated Testing with apictl
# Generate test suite from OpenAPI
apictl gen test -n EmployeeAPI -v 1.0.0 -e production
# Run tests
apictl test run -e production
Integration Tests (curl-based)
#!/bin/bash
BASE_URL="https://localhost:8243/employees/1.0.0"
TOKEN="Bearer <token>"
# Test GET /employees
echo "Testing GET /employees..."
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: $TOKEN" "$BASE_URL/employees")
[ "$STATUS" -eq 200 ] && echo "PASS" || echo "FAIL: $STATUS"
# Test POST /employees
echo "Testing POST /employees..."
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
-X POST -H "Authorization: $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Test User","email":"test@example.com"}' \
"$BASE_URL/employees")
[ "$STATUS" -eq 201 ] && echo "PASS" || echo "FAIL: $STATUS"
Key Takeaways
- APIs can be created from scratch, from OpenAPI specs, or from WSDL
- Failover and load-balanced endpoints provide resilience
- SOAP-to-REST conversion requires mediation sequences for payload transformation
- GraphQL and WebSocket APIs are first-class citizens in APIM
- API Products bundle multiple APIs for simpler consumption
- Mediation policies customize request/response processing at the gateway
- apictl CLI enables CI/CD-friendly API management
Next Steps
Continue to Chapter 8: Message Mediation to learn about advanced message transformation and mediation sequences.