Skip to content

Commit 6b7d9a6

Browse files
committed
List and table styles to openapi-spec-documentation-sdk-generation-validation article
1 parent ce2a9b6 commit 6b7d9a6

2 files changed

Lines changed: 72 additions & 40 deletions

File tree

  • src/content/articles/openapi-spec-documentation-sdk-generation-validation

‎src/content/articles/openapi-spec-documentation-sdk-generation-validation/index.mdx‎

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@ featured: true
1313

1414
Most teams I work with treat OpenAPI specs as __output__ — something you generate from existing code and call "documentation." That's archaeology, not specification. Meanwhile, their hand-written docs in Confluence are perpetually out of date, their manually coded SDKs break when the API changes, and validation logic differs between the gateway and the backend.
1515

16-
The real value of OpenAPI emerges when you flip the relationship. The spec becomes the source of truth that drives your code, not the other way around. When the spec drives everything — validation middleware, CI checks, generated clients — consistency becomes automatic. The spec can't disagree with your validation because your validation __reads__ the spec. Documentation can't drift because it's generated from the same source.
16+
The real value of OpenAPI emerges when you flip the relationship. The spec becomes the source of truth that drives your code, not the other way around. When the spec drives everything — validation middleware, CI checks, generated clients — consistency becomes automatic. The spec can't disagree with your validation because your validation _reads_ the spec. Documentation can't drift because it's generated from the same source.
1717

1818
That's the theory. Here's how it works in practice, starting with the two areas that pay off immediately: request validation and CI automation.
1919

2020
## Request Validation: Where the Spec Earns Its Keep
2121

2222
Validation is where OpenAPI specs prove their worth. Instead of writing validation logic by hand — checking types, verifying formats, ensuring required fields exist — you derive it directly from the spec. The same schema that defines your API contract also enforces it at runtime.
2323

24-
The key is understanding what schema validation actually does. It handles __structural__ correctness: rejecting requests where `quantity` is a string instead of an integer, where `email` doesn't match email format, where required fields are missing. It won't reject a request where `customerId` is a valid UUID that doesn't exist in your database. That's business validation, and you still need it.
24+
The key is understanding what schema validation actually does. It handles _structural_ correctness: rejecting requests where `quantity` is a string instead of an integer, where `email` doesn't match email format, where required fields are missing. It won't reject a request where `customerId` is a valid UUID that doesn't exist in your database. That's business validation, and you still need it.
2525

2626
In Node.js/Express, `express-openapi-validator` is the standard choice. Point it at your spec file, and it automatically validates request bodies, query parameters, path parameters, and headers against your schemas:
2727

@@ -54,10 +54,11 @@ Code: Express middleware that validates requests against your OpenAPI spec.
5454

5555
The `validateResponses: true` option deserves attention. Enable it in development and staging — it catches cases where your implementation returns data that doesn't match your spec. That's an early warning that spec and code have drifted apart. You might disable it in production for performance, but keeping it on during development catches bugs before you ship.
5656

57-
Other frameworks have equivalent solutions. If you're in Python, FastAPI handles this automatically — your Pydantic type hints __are__ your validation schema, and FastAPI generates an OpenAPI spec from them. You get validation and documentation from the same source without any additional configuration. Rails has `committee` (Rack middleware that validates against a spec file), Laravel has `spectator`, Go has `kin-openapi` for Echo, Chi, and Gin. The pattern is the same across all of them: point middleware at your spec, let it reject malformed requests before they hit your business logic.
57+
Other frameworks have equivalent solutions. If you're in Python, FastAPI handles this automatically — your Pydantic type hints _are_ your validation schema, and FastAPI generates an OpenAPI spec from them. You get validation and documentation from the same source without any additional configuration. Rails has `committee` (Rack middleware that validates against a spec file), Laravel has `spectator`, Go has `kin-openapi` for Echo, Chi, and Gin. The pattern is the same across all of them: point middleware at your spec, let it reject malformed requests before they hit your business logic.
5858

5959
<Table
60-
variant="vertical-column-delineation-table"
60+
variant="grid-and-accent-header-table"
61+
fullWidth={false}
6162
content={{
6263
thead: {
6364
th: ['Framework', 'Library', 'Approach'],
@@ -66,19 +67,19 @@ Other frameworks have equivalent solutions. If you're in Python, FastAPI handles
6667
tr: [
6768
{
6869
th: 'Express/Node',
69-
td: ['`express-openapi-validator`', 'Middleware validates against spec file'],
70+
td: ['<span class="font-mono">express-openapi-validator</span>', 'Middleware validates against spec file'],
7071
},
7172
{
7273
th: 'FastAPI/Python',
7374
td: ['Built-in with Pydantic', 'Type hints become validation schema'],
7475
},
7576
{
7677
th: 'Rails',
77-
td: ['`committee`', 'Rack middleware validates against spec'],
78+
td: ['<span class="font-mono">committee</span>', 'Rack middleware validates against spec'],
7879
},
7980
{
8081
th: 'Go',
81-
td: ['`kin-openapi`', 'Middleware for Echo, Chi, Gin'],
82+
td: ['<span class="font-mono">kin-openapi</span>', 'Middleware for Echo, Chi, Gin'],
8283
},
8384
],
8485
},

‎src/content/articles/openapi-spec-documentation-sdk-generation-validation/pdf.mdx‎

Lines changed: 64 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,19 @@ If you've worked with OpenAPI specs before, you know they can get unwieldy fast.
3232
An OpenAPI spec has four top-level sections that matter:
3333

3434
<List
35-
variant="check-icons-list"
35+
variant="card-grid-list"
3636
items={[
3737
{
38-
text: '`info` for metadata',
38+
lead: '<span class="font-mono">info</span> for metadata',
3939
},
4040
{
41-
text: '`servers` for environment URLs',
41+
lead: '<span class="font-mono">servers</span> for environment URLs',
4242
},
4343
{
44-
text: '`paths` for your actual endpoints',
44+
lead: '<span class="font-mono">paths</span> for your actual endpoints',
4545
},
4646
{
47-
text: '`components` for reusable definitions',
47+
lead: '<span class="font-mono">components</span> for reusable definitions',
4848
},
4949
]}
5050
/>
@@ -184,8 +184,9 @@ Code: Using allOf to compose shared base properties into entity schemas.
184184
For polymorphic types — where a field can be one of several shapes — use `oneOf` with a discriminator. Payment methods are the classic example: credit cards have `last4` and `expiryMonth`, bank transfers have `routingNumber` and `accountNumber`, PayPal has `email`. The discriminator tells parsers which schema applies based on a `type` field.
185185

186186
<Table
187-
variant="vertical-column-delineation-table"
187+
variant="numbered-review-table"
188188
content={{
189+
figure: 'OpenAPI schema design patterns and when to use them',
189190
thead: {
190191
th: ['Pattern', 'When to Use'],
191192
},
@@ -196,11 +197,11 @@ For polymorphic types — where a field can be one of several shapes — use `on
196197
td: ['Create/update/read operations have different field requirements'],
197198
},
198199
{
199-
th: '`allOf` composition',
200+
th: '<span class="font-mono">allOf</span> composition',
200201
td: ['Multiple entities share identical base fields (audit timestamps, IDs)'],
201202
},
202203
{
203-
th: '`oneOf` polymorphism',
204+
th: '<span class="font-mono">oneOf</span> polymorphism',
204205
td: ['A field can be one of several distinct types with different structures'],
205206
},
206207
],
@@ -261,7 +262,7 @@ The workflow looks like this: design the API in your spec editor, review with st
261262
You'll want a decent editor for spec-first work. Here are the main options:
262263

263264
<Table
264-
variant="vertical-column-delineation-table"
265+
variant="grid-and-accent-header-table"
265266
content={{
266267
thead: {
267268
th: ['Editor', 'Best For'],
@@ -361,19 +362,22 @@ Once you have an OpenAPI spec, generating documentation is almost trivially easy
361362
The three main options cover different use cases:
362363

363364
<List
364-
variant="check-icons-list"
365+
variant="chevron-list"
366+
classes={{
367+
wrapper: "sm:ml-6 sm:mr-12 mb-8",
368+
}}
365369
items={[
366370
{
367371
lead: 'Redoc',
368-
text: 'generates static HTML from your spec. You get a three-panel layout (navigation, content, code samples), search, and nested schema expansion. It\'s a single HTML file you can host anywhere — GitHub Pages, S3, your CDN. No backend required. This is my default choice for most projects because it\'s zero-maintenance once deployed.',
372+
text: 'Generates static HTML from your spec. You get a three-panel layout (navigation, content, code samples), search, and nested schema expansion. It\'s a single HTML file you can host anywhere — GitHub Pages, S3, your CDN. No backend required. This is my default choice for most projects because it\'s zero-maintenance once deployed.',
369373
},
370374
{
371375
lead: 'Swagger UI',
372-
text: 'provides interactive documentation where developers can make live requests against your API. The "Try it out" button is genuinely useful for exploration and debugging. The tradeoff: you need to think about authentication, CORS, and whether you want people hitting your production API from the docs.',
376+
text: 'Provides interactive documentation where developers can make live requests against your API. The "Try it out" button is genuinely useful for exploration and debugging. The tradeoff: you need to think about authentication, CORS, and whether you want people hitting your production API from the docs.',
373377
},
374378
{
375379
lead: 'Stoplight Elements',
376-
text: 'is a React component library for embedding documentation in an existing portal or documentation site. If you\'re building a developer portal with tutorials, guides, and API reference together, Elements lets you integrate the API docs natively rather than linking out to a separate site.',
380+
text: 'A React component library for embedding documentation in an existing portal or documentation site. If you\'re building a developer portal with tutorials, guides, and API reference together, Elements lets you integrate the API docs natively rather than linking out to a separate site.',
377381
},
378382
]}
379383
/>
@@ -443,9 +447,13 @@ responses:
443447
```
444448
Code: Named examples for different order states.
445449
450+
These individual techniques — rich descriptions, multiple examples, error scenarios — compound when applied consistently across your spec. The table below summarizes which documentation elements to prioritize and what each should contain, so you can audit an existing spec or build a new one with the right coverage from the start.
451+
446452
<Table
447-
variant="vertical-column-delineation-table"
453+
variant="numbered-review-table"
454+
fullWidth={false}
448455
content={{
456+
figure: 'Documentation elements that improve developer experience.',
449457
thead: {
450458
th: ['Documentation Element', 'What to Include'],
451459
},
@@ -469,7 +477,6 @@ Code: Named examples for different order states.
469477
},
470478
],
471479
},
472-
figure: 'Documentation elements that improve developer experience.',
473480
}}
474481
/>
475482
@@ -490,25 +497,35 @@ The good news is that SDK generation has matured significantly. With the right g
490497
The SDK generator landscape breaks down into three categories: comprehensive multi-language tools, language-specific tools with better output, and types-only generators.
491498
492499
<List
493-
variant="check-icons-list"
500+
variant="zebra-list"
501+
classes={{
502+
wrapper: "sm:ml-6 sm:mr-12 mb-6",
503+
ul: "!divide-y-0 !border-0 !overflow-visible !rounded-none space-y-4",
504+
li: "!rounded-lg !border border-trim",
505+
}}
494506
items={[
495507
{
496-
lead: 'OpenAPI Generator',
497-
text: '(the open-source fork of Swagger Codegen) supports 50+ languages and is the default choice for teams that need SDKs in multiple languages from the same spec. The TypeScript output is decent, the Python output is usable, and you get consistent behavior across languages. The tradeoff is that "decent" and "usable" aren\'t "great"—the generated code tends toward verbosity and doesn\'t always follow language idioms.',
508+
lead: 'OpenAPI Generator (the open-source fork of Swagger Codegen)',
509+
text: 'Supports 50+ languages and is the default choice for teams that need SDKs in multiple languages from the same spec. The TypeScript output is decent, the Python output is usable, and you get consistent behavior across languages. The tradeoff is that "decent" and "usable" aren\'t "great"—the generated code tends toward verbosity and doesn\'t always follow language idioms.',
498510
},
499511
{
500512
lead: 'Orval',
501-
text: 'focuses exclusively on TypeScript but produces notably better output. It generates React Query hooks, Zod validation schemas, and MSW mock handlers out of the box. If your consumers are React developers, Orval\'s output feels like code they\'d actually write.',
513+
text: 'Focuses exclusively on TypeScript but produces notably better output. It generates React Query hooks, Zod validation schemas, and MSW mock handlers out of the box. If your consumers are React developers, Orval\'s output feels like code they\'d actually write.',
502514
},
503515
{
504-
lead: 'openapi-typescript',
505-
text: 'takes a different approach: it generates only TypeScript types, no runtime code. You get interfaces for your request/response bodies and use your own HTTP client. This is the lightest-weight option and works well when you want type safety without adopting a generated SDK wholesale.',
516+
lead: '<span class="font-mono">openapi-typescript</span>',
517+
text: 'Takes a different approach: it generates only TypeScript types, no runtime code. You get interfaces for your request/response bodies and use your own HTTP client. This is the lightest-weight option and works well when you want type safety without adopting a generated SDK wholesale.',
506518
},
507519
]}
508520
/>
509521
522+
These tools solve different problems, so the right choice depends less on raw feature count and more on who will consume the generated code. A frontend-heavy team may prioritize React Query integration and ergonomic TypeScript output, while a platform team supporting multiple languages may accept less idiomatic clients in exchange for broader coverage. The comparison below makes those tradeoffs explicit.
523+
510524
<Table
511-
variant="vertical-column-delineation-table"
525+
variant="grid-and-accent-header-table"
526+
classes={{
527+
tbody: '!text-content',
528+
}}
512529
content={{
513530
thead: {
514531
th: ['Generator', 'Languages', 'Best For'],
@@ -636,7 +653,7 @@ For Python, FastAPI handles this automatically if you're using Pydantic models.
636653
Other ecosystems have their own solutions:
637654

638655
<Table
639-
variant="vertical-column-delineation-table"
656+
variant="criteria-highlight-table"
640657
content={{
641658
thead: {
642659
th: ['Framework', 'Library', 'Approach'],
@@ -645,23 +662,23 @@ Other ecosystems have their own solutions:
645662
tr: [
646663
{
647664
th: 'Rails',
648-
td: ['`committee`', 'Rack middleware that validates against OpenAPI specs'],
665+
td: ['<span class="font-mono">committee</span>', 'Rack middleware that validates against OpenAPI specs'],
649666
},
650667
{
651668
th: 'Laravel',
652-
td: ['`spectator`', 'Request/response validation with PHPUnit integration'],
669+
td: ['<span class="font-mono">spectator</span>', 'Request/response validation with PHPUnit integration'],
653670
},
654671
{
655672
th: '.NET',
656-
td: ['`Microsoft.AspNetCore.OpenApi`', 'Built-in validation in ASP.NET Core 9+; earlier versions use `NSwag`'],
673+
td: ['<span class="font-mono">Microsoft.AspNetCore.OpenApi</span>', 'Built-in validation in ASP.NET Core 9+; earlier versions use <span class="font-mono">NSwag</span>'],
657674
},
658675
{
659676
th: 'Go',
660-
td: ['`kin-openapi`', 'Middleware for Echo, Chi, Gin; validates requests and responses'],
677+
td: ['<span class="font-mono">kin-openapi</span>', 'Middleware for Echo, Chi, Gin; validates requests and responses'],
661678
},
662679
{
663680
th: 'Spring Boot',
664-
td: ['`springdoc-openapi`', 'Code-first with automatic spec generation and validation'],
681+
td: ['<span class="font-mono">springdoc-openapi</span>', 'Code-first with automatic spec generation and validation'],
665682
},
666683
],
667684
},
@@ -714,7 +731,8 @@ Code: Error handler that transforms technical validation errors into readable me
714731
It's important to understand the boundaries of schema validation:
715732

716733
<Table
717-
variant="vertical-column-delineation-table"
734+
variant="grid-and-accent-header-table"
735+
fullWidth={false}
718736
content={{
719737
thead: {
720738
th: ['Validation Catches', 'Validation Misses'],
@@ -854,22 +872,32 @@ Breaking changes are the silent killer of API reliability. Someone adds a requir
854872
`openapi-diff` compares two spec versions and flags incompatible changes:
855873

856874
<List
857-
variant="check-icons-list"
875+
variant="colored-marker-list"
876+
size={3}
877+
classes={{
878+
wrapper: "sm:ml-6 sm:mr-12 mb-6",
879+
text: "pt-1",
880+
}}
858881
items={[
859882
{
860883
text: 'Removed endpoints',
884+
color: "bg-danger-offset",
861885
},
862886
{
863887
text: 'Removed or renamed fields',
888+
color: "bg-danger-offset",
864889
},
865890
{
866891
text: 'New required fields in request bodies',
892+
color: "bg-danger-offset",
867893
},
868894
{
869895
text: 'Changed field types',
896+
color: "bg-danger-offset",
870897
},
871898
{
872899
text: 'Narrowed enum values',
900+
color: "bg-danger-offset",
873901
},
874902
]}
875903
/>
@@ -881,15 +909,18 @@ Run it in PR checks with `--fail-on-incompatible` to block merges that would bre
881909
Contract tests verify your implementation actually matches your spec. Dredd and Schemathesis are the main tools:
882910

883911
<List
884-
variant="check-icons-list"
912+
variant="chevron-list"
913+
classes={{
914+
wrapper: "sm:ml-6 sm:mr-12 mb-6",
915+
}}
885916
items={[
886917
{
887918
lead: 'Dredd',
888-
text: 'reads your spec, generates requests for each endpoint, hits your running server, and validates responses against the schema. It\'s deterministic — same spec produces same tests.',
919+
text: 'Reads your spec, generates requests for each endpoint, hits your running server, and validates responses against the schema. It\'s deterministic — same spec produces same tests.',
889920
},
890921
{
891922
lead: 'Schemathesis',
892-
text: 'takes a property-based testing approach. It generates random valid inputs based on your schemas and looks for edge cases that break your implementation. It\'s better at finding bugs but less predictable.',
923+
text: 'Takes a property-based testing approach. It generates random valid inputs based on your schemas and looks for edge cases that break your implementation. It\'s better at finding bugs but less predictable.',
893924
},
894925
]}
895926
/>

0 commit comments

Comments
 (0)