You design your API contract before you write a single line of code, and the Swagger definition is that contract.
Imagine you’re building a service that manages user profiles. Before you write any Go, Python, or Java code, you’d define what a user profile looks like and what operations you can perform on it.
openapi: 3.0.0
info:
title: User Profile API
version: 1.0.0
paths:
/users/{userId}:
get:
summary: Get a user profile
parameters:
- name: userId
in: path
required: true
schema:
type: integer
format: int64
responses:
'200':
description: User profile retrieved successfully
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'404':
description: User not found
put:
summary: Update a user profile
parameters:
- name: userId
in: path
required: true
schema:
type: integer
format: int64
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/User'
responses:
'200':
description: User profile updated successfully
'404':
description: User not found
components:
schemas:
User:
type: object
properties:
id:
type: integer
format: int64
username:
type: string
email:
type: string
format: email
required:
- id
- username
This OpenAPI (formerly Swagger) definition is your single source of truth. It specifies the API’s endpoints (/users/{userId}), the HTTP methods allowed (GET, PUT), the expected parameters (a path parameter userId of type integer), the request body schema (a User object), and the possible responses (status codes and their content).
This upfront definition forces collaboration. The frontend team can start building their UI, mocking responses based on this spec, while the backend team can use tools to generate server stubs or client SDKs directly from this YAML. This eliminates the "it works on my machine" problem and ensures both sides are building against the same agreed-upon contract.
The "design-first" approach means you’re not just documenting code; you’re defining the interface of your system. This interface then drives the development of both the server and client implementations. Frameworks like Swagger Codegen or OpenAPI Generator can take this YAML file and produce boilerplate code for your server framework (e.g., Spring Boot, Express.js) or client SDKs in various languages.
For example, if you’re using swagger-codegen with a Java Spring Boot template, you’d run a command like this:
swagger-codegen generate -i swagger.yaml -l spring -o ./generated-server
This command takes your swagger.yaml file, uses the spring generator, and outputs a fully scaffolded Spring Boot project with controller interfaces and model classes already defined according to your spec. You then fill in the implementation details.
The most surprising thing about OpenAPI is how it transforms API development from an iterative, often error-prone process of writing code and then documenting it, into a declarative, specification-driven workflow. The spec is the primary artifact, and code generation becomes a natural consequence of that. This means if you change the YAML, you can regenerate code for both server and client, keeping everything in sync with minimal manual effort.
When you define a discriminator in your components/schemas section, you’re telling OpenAPI how to differentiate between different types of objects that share a common interface. For instance, if you have a Shape schema with subtypes like Circle and Square, the discriminator field (e.g., shapeType) in the request or response will indicate which specific subtype is being used. This is crucial for polymorphic data structures, allowing clients and servers to correctly serialize and deserialize complex object hierarchies without ambiguity.
The next step is understanding how to version your API using OpenAPI specifications.