RAML and Swagger (now OpenAPI Specification) aren’t just different ways to describe APIs; they’re fundamentally different philosophies about how APIs should be described. The most surprising truth is that while both aim for machine-readable API definitions, their core design choices lead to vastly different developer experiences and architectural implications.
Let’s see RAML in action with a simple example. Imagine defining a basic "Hello, World!" API:
#%RAML 1.0
title: Greeting API
version: v1
baseUri: http://localhost:8080/{version}
/hello:
get:
description: Returns a greeting message.
responses:
200:
body:
application/json:
schema: |
{
"type": "object",
"properties": {
"message": { "type": "string" }
}
}
example: |
{
"message": "Hello, World!"
}
Now, let’s look at the equivalent in OpenAPI (Swagger 2.0 for this example, though OpenAPI 3.x is more common now):
swagger: "2.0"
info:
title: Greeting API
version: v1
host: localhost:8080
basePath: /v1
schemes:
- http
paths:
/hello:
get:
summary: Returns a greeting message.
produces:
- application/json
responses:
"200":
description: Successful response
schema:
type: object
properties:
message:
type: string
example:
message: "Hello, World!"
This immediately highlights a key difference: RAML uses YAML’s native structure for much of its definition, feeling more like a natural extension of YAML. OpenAPI, on the other hand, uses a JSON-like structure (even when written in YAML) with explicit keywords like swagger, info, paths, etc.
The problem RAML and OpenAPI solve is the ambiguity and manual effort involved in understanding, consuming, and building APIs. Without a standardized, machine-readable description, developers rely on outdated documentation, informal agreements, or trial-and-error. This leads to integration issues, increased development time, and brittle systems.
Internally, both languages parse the definition into a structured model. However, RAML’s design emphasizes a top-down, resource-oriented approach. Its features like traits, types, and libraries encourage reusability and a more declarative style. You can define common responses or parameters once and apply them across multiple endpoints.
OpenAPI, especially in its earlier versions, felt more like a direct translation of HTTP concepts. While it has evolved significantly with OpenAPI 3.x to incorporate more advanced features like components, callbacks, and explicit schema definitions, its roots are still very much in describing the HTTP interaction itself.
Consider how you’d define reusable parameters. In RAML, you might have a types section and then reference them:
#%RAML 1.0
title: My API
types:
UserId:
type: integer
description: The unique identifier for a user.
/users/{userId}:
get:
queryParameters:
userId:
type: UserID # Reusing the defined type
In OpenAPI 3.x, you’d use components/parameters:
openapi: 3.0.0
info:
title: My API
version: 1.0.0
paths:
/users/{userId}:
get:
parameters:
- name: userId
in: path
required: true
schema:
type: integer
description: The unique identifier for a user.
example: 123
components:
parameters:
UserIdParam: # A name for the reusable parameter
name: userId
in: path
required: true
schema:
type: integer
description: The unique identifier for a user.
example: 123
The levers you control in both are similar: the structure of your API endpoints, the request and response payloads (schemas), error codes, authentication methods, and metadata. However, RAML’s focus on types and traits often leads to a more compact and arguably more maintainable definition for large APIs. OpenAPI’s strength lies in its widespread adoption and the vast ecosystem of tools built around it, from code generators to testing frameworks.
One thing most people don’t grasp immediately is how RAML’s traits and resource types can fundamentally alter the shape of your API definition, allowing for a highly abstract and reusable design that goes beyond simple parameter or schema reuse. You can define entire resource behaviors and apply them, making it possible to enforce consistency across very large, complex APIs with minimal repetition. For instance, a searchable trait could automatically add query parameters for pagination and filtering, along with standardized response structures for such operations, to any resource it’s applied to.
The next concept to explore is how these descriptions are used to generate client SDKs and server stubs, and the trade-offs involved in choosing one specification over the other for a given project.