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

TypeDescriptionUse Case
HTTPStandard REST backendMost common
AddressSOAP/generic endpointLegacy backends
FailoverPrimary + backup endpointsHigh availability
Load BalanceDistribute across backendsScalability
DynamicResolved at runtimeMulti-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 Authentication
  • DIGEST: HTTP Digest Authentication
  • OAUTH: 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:

OperationThrottling Policy
Query.employeesUnlimited
Query.employeeGold
Mutation.createEmployeeSilver
Mutation.deleteEmployeeBronze

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

StrategyExampleWhen to Use
URL path/v1/employeesMost common, clear
Query param/employees?v=1Backwards compatible
HeaderAccept-Version: 1Clean 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:

  1. Never break existing consumers silently
  2. Deprecate old versions with a grace period (3-6 months minimum)
  3. Communicate changes via Developer Portal changelogs
  4. 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.