Jesse A. Green / notebook

Live demo: the DocCompiler guide library as a static, read-only snapshot (October 2026). No backend, no accounts, nothing to submit.

RealWorld (Conduit) — User Guide

Generated May 2026 from realworld-apps/realworld at e75fef3

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

1. Quick Start

RealWorld is a standardized specification for building a Medium.com clone called Conduit. It defines a complete REST API, frontend templates, and end-to-end test suites so that any framework implementation can be compared apples-to-apples. The repository itself contains no application code — it provides the spec, test infrastructure, and documentation site that implementations conform to.

Key features:

  • OpenAPI 3.1 specification defining 15 REST endpoints for a blogging platform
  • Hurl test suite for backend API compliance validation
  • Bruno test collection (auto-generated from Hurl) for interactive API exploration
  • Playwright end-to-end test suite covering 13 spec areas (auth, articles, comments, social, error handling, XSS security)
  • Astro/Starlight documentation site with full specification details
  • Mobile icon assets for iOS, Android, watchOS, and iMessage implementations
  • Conduit logo SVG generator using OpenType.js

Prerequisites: Node.js (for docs site), Bun (for test conversion tooling), Hurl or Bruno (for API tests), Playwright (for E2E tests)

Quickest way to validate a backend:

HOST=http://localhost:3000 bash specs/api/run-api-tests-hurl.sh
Running Hurl tests against http://localhost:3000 with uid=1716000000$$

Tip

If you just want to explore the API interactively, import the Bruno collection at specs/api/bruno/ into the Bruno app and select the local environment.

Next steps: Install your preferred test runner (see Installation), point it at your implementation's URL, and run the compliance suite.

2. Installation

System Requirements

Component Version Purpose
Node.js 18+ Documentation site (docs/)
Bun 1.0+ Hurl-to-Bruno converter, logo generator
Hurl 4.0+ Backend API compliance tests
Bruno CLI 1.0+ Alternative API test runner
Playwright Latest Frontend E2E tests

Installing the Documentation Site

cd docs
npm install
added 450 packages in 12s

Important

The documentation site is a standalone Astro project inside the docs/ directory with its own package.json. Do not run npm install from the repository root.

Verifying the Docs Dev Server

cd docs
npm run dev
🚀 astro dev server running at localhost:4321

Installing Test Tooling

Hurl

# macOS
brew install hurl

# Linux (via cargo)
cargo install hurl

Bruno CLI

npm install -g @usebruno/cli

Playwright

npx playwright install --with-deps chromium

Post-Install Verification

hurl --version
hurl 4.3.0
bru --version
1.x.x

3. Configuration

RealWorld's test infrastructure uses two environment variables to target your implementation:

Variable Default Purpose
HOST http://localhost:8000 (Hurl), http://localhost:3000 (Bruno) Base URL for API test runners
API_BASE https://api.realworld.show/api Base URL for Playwright E2E tests
API_MODE true When false, E2E tests skip API-only scenarios and use form-based flows

Note

HOST is consumed by the shell scripts run-api-tests-hurl.sh and run-api-tests-bruno.sh. API_BASE and API_MODE are consumed by the Playwright test helpers in specs/e2e/helpers/config.ts.

The Bruno environment file at specs/api/bruno/environments/local.bru sets:

vars {
  host: http://localhost:3000
}

Override it at runtime with --env-var "host=http://your-server:8080".

For the documentation site, configuration lives in docs/astro.config.mjs and docs/tsconfig.json. The TypeScript config extends astro/tsconfigs/strict with JSX support for React components:

{
  "extends": "astro/tsconfigs/strict",
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "react"
  }
}

See the Admin Guide for the complete configuration reference.

4. Core Usage

Running Backend API Compliance Tests

The primary use case is validating that your RealWorld backend implementation conforms to the Conduit API specification.

HOST=http://localhost:3000 bash specs/api/run-api-tests-hurl.sh

The script discovers all .hurl files in specs/api/hurl/ and runs them sequentially (--jobs 1) against the target host. Each file covers one domain:

Test File Coverage
auth.hurl Registration, login, user CRUD, nullable field normalization
articles.hurl Article CRUD, tag management, tag removal with empty array
comments.hurl Comment CRUD, selective deletion
favorites.hurl Favorite/unfavorite, filter by favorited username
feed.hurl Follow-based feed, pagination (limit + offset)
profiles.hurl Profile fetch (with/without auth), follow/unfollow
tags.hurl Tag listing
pagination.hurl Limit and offset parameters
errors_auth.hurl Registration validation, login errors, auth-required endpoints
errors_articles.hurl Article validation, unknown slugs, unauthorized access
errors_authorization.hurl Cross-user article/comment modification (403)
errors_comments.hurl Comment validation, unknown article/comment errors
errors_profiles.hurl Unknown profile, unauthenticated follow

Tip

Run a single test file to debug a specific area: HOST=http://localhost:3000 hurl --test specs/api/hurl/auth.hurl --variable "host=http://localhost:3000" --variable "uid=$(date +%s)$$".

With Bruno

HOST=http://localhost:3000 bash specs/api/run-api-tests-bruno.sh

Or run specific folders:

bru run specs/api/bruno/auth --env local --env-var "host=http://localhost:3000"

Warning

The Bruno collection is auto-generated from the Hurl test files. Do not edit .bru files directly — your changes will be overwritten. Edit the .hurl source files instead, then regenerate with bun specs/api/hurl-to-bruno.js.

Verifying Bruno Collection is in Sync

bun specs/api/hurl-to-bruno.js --check
Bruno collection is up to date.

If out of sync:

Bruno collection is out of sync with Hurl files:
  changed: auth/01-register.bru
  missing: auth/20-verify-username-email-update-persisted.bru

Run `bun api/hurl-to-bruno.js` to regenerate.

Running Frontend E2E Tests

The Playwright test suite validates that a frontend implementation renders correctly and handles user interactions properly.

API_BASE=http://localhost:3000/api npx playwright test --config specs/e2e/playwright.base.ts

Important

Implementations should create their own playwright.config.ts that extends the base config and adds a webServer block to start the app automatically. See the base config at specs/e2e/playwright.base.ts for the full reference.

The E2E suite covers 13 spec areas with over 80 test cases:

Spec File Tests Coverage
auth.spec.ts 8 Register, login, logout, session persistence, invalid token handling
articles.spec.ts 10 CRUD, favorites, tag removal, author-only edit/delete
comments.spec.ts 8 Add, delete, multi-comment, auth-required, author-only delete
navigation.spec.ts 9 Feed switching, tag filtering, pagination, profile tabs
social.spec.ts 5 Follow/unfollow, profile views, feed from followed users
settings.spec.ts — User settings form
error-handling.spec.ts 18 400/401/403/500/network errors across all operations
user-fetch-errors.spec.ts 12 App initialization error handling (4XX logout, 5XX unavailable mode)
null-fields.spec.ts 11 Default avatars, null bio handling, image clearing
url-navigation.spec.ts 7 URL-based feed/tag routing, query parameters
health.spec.ts 4 Basic app loading, API accessibility
xss-security.spec.ts 12 Image URL injection, markdown body XSS, description sanitization

Working with the Documentation Site

cd docs
npm run dev       # Dev server at localhost:4321
npm run build     # Production build to ./dist/
npm run preview   # Preview production build locally

Content lives in docs/src/content/docs/ as .md and .mdx files. Each file becomes a route based on its filename.

5. Common Tasks

Validate a New Backend Implementation

Goal: Confirm your backend passes the full RealWorld API specification.

Prerequisites: Your backend running and accepting requests.

Steps:

  1. Start your backend server
  2. Run the Hurl test suite:
    HOST=http://localhost:YOUR_PORT bash specs/api/run-api-tests-hurl.sh
    
  3. Review any failures — each test has descriptive comments explaining the expected behavior
  4. Fix your implementation and re-run

If it fails: Check that your API matches the OpenAPI spec at specs/api/openapi.yml. Common issues include wrong response envelope structure (e.g., { "user": {...} } wrapping) and missing nullable field handling.

Regenerate the Bruno Collection from Hurl

Goal: Update the Bruno collection after modifying Hurl test files.

bun specs/api/hurl-to-bruno.js
Generated 145 files in api/bruno/

Add a New API Test Case

Goal: Extend the test suite with additional validation.

  1. Edit the appropriate .hurl file in specs/api/hurl/
  2. Add your test with a comment header, HTTP method, assertions:
    # Verify article slug format
    GET {{host}}/api/articles
    HTTP 200
    [Asserts]
    jsonpath "$.articles[0].slug" matches "^[a-z0-9-]+$"
    
  3. Run to verify: HOST=http://localhost:3000 hurl --test specs/api/hurl/articles.hurl --variable "host=http://localhost:3000" --variable "uid=$(date +%s)$$"
  4. Regenerate Bruno: bun specs/api/hurl-to-bruno.js

Run E2E Tests Against a Fullstack Implementation

Goal: Validate both frontend and backend together.

  1. Create a playwright.config.ts in your implementation root:
    import { defineConfig } from '@playwright/test';
    import { baseConfig } from './e2e/playwright.base';
    
    export default defineConfig({
      ...baseConfig,
      use: { ...baseConfig.use, baseURL: 'http://localhost:3000' },
      webServer: {
        command: 'npm run start',
        url: 'http://localhost:3000',
        reuseExistingServer: !process.env.CI,
        timeout: 120_000,
      },
    });
    
  2. Copy or symlink the specs/e2e/ directory into your project
  3. Run: npx playwright test

Tip

Set API_MODE=false to test fullstack implementations that don't expose a separate API. This skips tests that use page.route() API mocking and falls back to form-based flows.

Implement the Debug Interface for Better E2E Coverage

The E2E suite uses a window.__conduit_debug__ interface for auth-state assertions:

interface ConduitDebug {
  getToken: () => string | null;
  getAuthState: () => 'authenticated' | 'unauthenticated' | 'unavailable' | 'loading';
  getCurrentUser: () => User | null;
}

Expose this on window.__conduit_debug__ in your frontend to enable token-state validation tests.

Generate the Conduit Logo SVG

cd assets/media
bun conduit-logo.svg.generate.ts
Downloading Caudex Bold...
Written to conduit-logo.svg (147.2 x 35.8)

The script downloads the Caudex Bold font, renders "Conduit" as SVG paths using OpenType.js, and writes conduit-logo.svg.

6. Troubleshooting

Error: Unhandled hurl assert (not jsonpath)

Cause: A Hurl assertion uses a type not supported by the hurl-to-bruno converter (e.g., header, cookie, or body assertions instead of jsonpath).

Solution: The converter only handles jsonpath assertions. If your test needs non-jsonpath assertions, keep them in the Hurl file only — they won't be translated to Bruno.

Prevention: Write API assertions using jsonpath patterns when you need cross-format compatibility.

Error: Failed to register user: 409

Cause: The E2E test tried to register a user whose username or email already exists. This happens when test isolation breaks down (e.g., shared database between runs without cleanup).

Solution:

  1. Ensure your backend starts with a clean database for each test run, or
  2. Verify that generateUniqueUser() produces unique timestamps (it uses Date.now())

Prevention: Use the provided afterEach hooks that close browser contexts between tests.

Bruno Collection Out of Sync

Cause: Someone edited a .hurl file but didn't regenerate the Bruno collection.

Solution:

bun specs/api/hurl-to-bruno.js

Prevention: Run bun specs/api/hurl-to-bruno.js --check in CI to catch drift.

E2E Tests Fail with Timeouts on Article Pages

Cause: Running 6+ Playwright tests in sequence can exhaust browser resources (network connections, file descriptors). The test suite includes 500ms–1000ms cooldown delays in afterEach hooks to mitigate this.

Solution: Ensure tests run with workers: 1 (configured in playwright.base.ts) and that browser contexts are properly closed.

Warning

The fullyParallel: false and workers: 1 settings in the base Playwright config are intentional. Running tests in parallel causes flaky failures due to shared backend state and resource exhaustion.

Tags Test Assertion Order Incorrect

Cause: Known issue (#839). The test expectation for tag ordering may not match the backend's actual tag return order.

Solution: Check whether your backend returns tags in insertion order or alphabetical order, and adjust the test expectation accordingly.

Invalid Email Accepted During Registration

Cause: Known issue (#1584, 8 comments). The specification does not mandate server-side email format validation, so some backends accept malformed emails.

Solution: Implement email format validation in your backend's registration endpoint if desired.

Postman Regex Rejects Valid ISO-8601 Timestamps

Cause: Known issue (#832). The legacy Postman collection's timestamp regex is too strict for valid ISO-8601 variations.

Solution: Use the Hurl or Bruno test suites instead — they use proper assertion types rather than regex matching for timestamps.

Note

The Postman collection is legacy. The canonical test suites are Hurl (source of truth) and Bruno (auto-generated from Hurl).

Error: Registration failed: or Login failed:

Cause: The Playwright auth helper received an error response from the backend. The error message from .error-messages is appended.

Solution:

  1. Check that your backend is running and reachable at the configured API_BASE
  2. Verify your registration/login endpoint returns the expected { "user": { "token": "..." } } envelope
  3. Check for duplicate username/email if registering

Prevention: Use generateUniqueUser() which generates timestamp-based unique credentials.

Caution

The default API_BASE points to https://api.realworld.show/api — the live demo server. If you're testing a local implementation, always set API_BASE explicitly or you'll be running tests against the public demo.

7. FAQ

Q: Is this an application I can deploy?

No. RealWorld is a specification for building a Medium.com clone. It provides the API contract, test suites, and documentation. You need a separate implementation (backend and/or frontend) that conforms to this spec. Browse implementations at the RealWorld GitHub organization.

Q: What is the difference between Hurl and Bruno tests?

Hurl is a command-line HTTP testing tool — the .hurl files are the source of truth for API compliance tests. Bruno is an API client (like Postman) with both a GUI app and CLI runner. The Bruno collection is auto-generated from the Hurl files via specs/api/hurl-to-bruno.js. Use Hurl for CI/scripted validation; use Bruno for interactive exploration.

Q: How does authentication work in the API spec?

The API uses JWT tokens. Register via POST /api/users or login via POST /api/users/login to receive a token. Pass it in the Authorization header as Token xxxxxx.yyyyyyy.zzzzzz (not Bearer).

Important

The token scheme is Token, not Bearer. This is a deliberate RealWorld convention. Implementations must accept the Token prefix in the Authorization header.

Q: What does API_MODE control in E2E tests?

When API_MODE is true (default), tests use direct API calls for setup (faster) and include tests that mock API responses via page.route(). When false, tests use form-based UI flows for all operations and skip API-only test scenarios. Set it to false for fullstack implementations without a separate API endpoint.

Q: How are nullable fields handled?

The spec defines bio and image as nullable (type: [string, null]). Empty strings should be normalized to null by the backend. The E2E test suite (null-fields.spec.ts) validates that frontends show a default avatar SVG when image is null/empty and never render the literal text "null" for bio.

Q: Can I use this with a backend that returns 200 instead of 204 for DELETE?

Yes. The E2E tests explicitly verify that frontends handle both 200 and 204 for delete operations, per RFC 9110 (clients should accept any 2XX as success).

Q: Where is the OpenAPI specification?

At specs/api/openapi.yml. It defines all 15 endpoints, request/response schemas, authentication, and error formats. The API version is 2.0.0 and the spec uses OpenAPI 3.1.0.

Q: How do I contribute a new test case?

See CONTRIBUTING.md in the repository root. For API tests, add to the appropriate .hurl file and regenerate Bruno. For E2E tests, create or extend a .spec.ts file in specs/e2e/, using the helpers in specs/e2e/helpers/.

Q: What is the uid variable in Hurl tests?

Each test run generates a unique uid value (from $(date +%s)$$) that's used to create unique usernames and emails. This ensures test isolation — each run creates its own users and articles without colliding with previous runs.