← Back to Case Studies
Backend Architecture8 minAug 30, 2026

Designing a Secure Editorial API for a Bilingual Technical Publication

A backend engineering case study covering the API, security controls and publishing workflow behind Bodan the Backend.

Designing a Secure Editorial API for a Bilingual Technical Publication

Bodan the Backend: Building a Bilingual Editorial Platform with Static Generation, APIs, AI Automation, and Cloudflare Workers#

Bodan the Backend started as a technical portfolio and gradually evolved into a bilingual editorial platform with content management, static site generation, APIs, automated news workflows, and integrations with AI services.

The goal was never just to build a website that could publish articles.

I wanted to build a system that demonstrated how I approach backend engineering: how I separate responsibilities, design service boundaries, manage third-party integrations, reduce operational risk, and turn repetitive manual processes into reproducible workflows.

This case study covers the main technical decisions behind the project, the architecture of its APIs, the security controls built into the system, and the limitations that still remain.

The initial problem#

Publishing technical content in both Spanish and English required repeating too many manual steps.

For every article, I had to:

  • Create the Spanish and English versions manually.
  • Keep titles, descriptions, and metadata synchronized.
  • Update indexes and navigation pages.
  • Generate canonical URLs and hreflang relationships.
  • Manage images, reading time, and related content.
  • Rebuild and deploy the site after every change.
  • Verify that news items appeared correctly across all relevant views.

This workflow was manageable with a small number of publications, but it became increasingly fragile as the amount of content grew.

The more content I added, the easier it became for the two language versions to drift apart or for metadata, navigation, and generated pages to become inconsistent.

I also wanted to introduce current technology news into the platform without turning the site into an automated content aggregator.

More importantly, I did not want an AI model to have direct publishing authority.

Technical goals#

I defined five main goals for the architecture:

  1. Keep public content fast, static, and easy for search engines to crawl.
  2. Separate editorial administration from the public-facing website.
  3. Automate repetitive tasks without removing human review.
  4. Introduce dynamic services only where runtime execution was actually necessary.
  5. Design predictable failure modes for databases and external providers.

The resulting architecture combines pre-generated content with a relatively small dynamic layer running on Cloudflare Workers.

At a high level, the system is divided into three areas.

Public site#

Articles and news pages are generated as HTML before deployment.

When a user requests an article, the browser receives the full page in the initial response. Rendering the publication does not depend on a database query or an API request executed at page load.

That choice has several advantages.

It reduces production complexity, improves crawlability, lowers runtime dependencies, and ensures that already published articles remain readable even if a dynamic service such as Cloudflare D1 becomes unavailable.

For content that changes infrequently but is read repeatedly, static generation provides a better tradeoff than querying a database on every request.

Editorial administration#

The CMS runs inside the administration environment rather than as part of the public site.

It supports workflows such as:

  • Creating and editing publications.
  • Maintaining Spanish and English versions.
  • Saving drafts.
  • Scheduling publications.
  • Managing images.
  • Previewing private content.
  • Approving or rejecting news candidates.
  • Rebuilding public pages.

Markdown files remain the editorial source of truth.

Generated HTML files are treated as deployment artifacts. They are produced by the system and are not meant to be edited manually.

This separation makes the content easier to version, review, regenerate, and move between environments.

Dynamic services#

Only operations that require runtime state or external communication are handled dynamically.

Cloudflare Workers processes operations such as:

  • Newsletter subscriptions.
  • Newsletter unsubscribes.
  • Canonical redirects.
  • Cache behavior.
  • External provider integrations.

Cloudflare D1 stores the local state of newsletter subscribers.

Kit handles the email-specific responsibilities, including confirmation, campaign delivery, bounces, complaints, and unsubscribes.

This means D1 acts as the application's local source of runtime state, while Kit remains responsible for email delivery and subscriber lifecycle events.

API design#

The API surface is divided according to exposure and responsibility.

Bodan Api

Public API#

The public API is intentionally small:

POST /api/newsletter
POST /api/newsletter/unsubscribe

A subscription request follows this flow:

Request
   │
   ▼
Validate Content-Type
   │
   ▼
Normalize email and language
   │
   ▼
Verify consent
   │
   ▼
Apply rate limiting
   │
   ▼
Store pending status in D1
   │
   ▼
Register email in Kit
   │
   ▼
Send double opt-in confirmation

The API distinguishes between different failure classes instead of returning a generic error for every case.

For example, it handles:

  • Invalid request data.
  • Unsupported HTTP methods.
  • Excessive requests.
  • Temporary failures from external providers.

The goal is to keep API behavior explicit enough for the client while avoiding unnecessary exposure of internal implementation details.

Editorial API#

The administration environment uses a separate API for authentication, publications, and news management:

POST /api/admin/login
POST /api/admin/logout
GET  /api/admin/articles
POST /api/admin/articles
PUT  /api/admin/articles/:slug
GET  /api/admin/noticias/items
PUT  /api/admin/noticias/items/:id
POST /api/admin/noticias/items/:id/image
POST /api/admin/noticias/items/:id/approve
POST /api/admin/noticias/items/:id/reject

This API is not part of the public production surface.

The CMS and editorial data remain isolated inside the administration environment.

That separation reduces the number of privileged endpoints exposed publicly and keeps content-management concerns outside the runtime path used by readers.

AI-assisted editorial automation#

The news pipeline collects candidate stories from RSS feeds and Hacker News.

Before any content is passed further into the pipeline, the system validates that remote URLs are public and applies response-size limits.

The workflow deliberately separates evaluation from writing:

Sources
   │
   ▼
Retrieval and validation
   │
   ▼
Editor model
   │
   ├── Reject
   ├── Send for review
   └── Approve as candidate
            │
            ▼
       Writer model
            │
            ▼
       Human review
            │
            ▼
         Publication

The editor model evaluates whether a story is worth processing further.

Its job is to consider factors such as:

  • Technical relevance.
  • Source quality.
  • Whether the topic adds value to the site.
  • Whether there is enough room for original analysis.

Only candidates that pass this stage are sent to the writer model.

The writer model therefore does not decide what deserves to be published. It only operates on stories that have already passed an editorial filter.

This separation prevents evaluation and production from being mixed into a single model call and avoids spending generation resources on weak candidates.

Most importantly, neither model has permission to publish content directly.

Human review is always required before publication.

Backend security#

Security controls were designed as part of the main application flows instead of being added after the architecture was already complete.

The system includes:

  • Administrative sessions using HttpOnly cookies.
  • SameSite=Strict cookie policies.
  • CSRF protection for administrative operations.
  • Input validation and normalization.
  • Newsletter rate limiting.
  • Image format and size restrictions.
  • Limits on feeds, documents, and external responses.
  • Blocking of private and local network addresses when processing remote sources.
  • Redirect validation to reduce SSRF exposure.
  • Secrets stored outside the source code.
  • Error responses that avoid leaking credentials or internal implementation details.
  • CSP, HSTS, and additional browser security policies.

The remote-content validation logic is especially important because the news pipeline processes URLs provided by external sources.

Without network restrictions and redirect validation, a fetch mechanism could potentially be abused to access internal or private network resources.

The newsletter unsubscribe endpoint also avoids exposing subscriber existence.

It returns the same response whether the email address exists or not.

This prevents the endpoint from being used as an email-enumeration mechanism.

Key architectural decisions#

Static generation instead of dynamic rendering#

Articles change relatively infrequently but may be requested many times.

Querying a database on every page view would have introduced additional cost, latency, and failure modes without providing a meaningful benefit.

Generating the pages ahead of time makes it possible to serve complete content directly from edge infrastructure.

It also keeps content availability independent from the runtime database.

D1 only for dynamic state#

Cloudflare D1 is used only for information that genuinely needs to change while the application is running.

Subscriber state is a good example.

Editorial content, on the other hand, remains in version-controlled files.

This keeps the database from becoming a dependency for content that can be generated ahead of time.

Separate editor and writer models#

Initially, using a single AI model to both evaluate and write a news item seemed simpler.

In practice, it combined two very different responsibilities.

Content selection is a classification and judgment problem.

Content writing is a generation problem.

Separating them makes the pipeline easier to reason about and allows low-quality candidates to be rejected before spending additional resources on generation.

Mandatory human review#

The purpose of automation is to reduce repetitive work, not to remove editorial responsibility.

Human review remains a hard requirement before publication.

Besides maintaining editorial control, this helps catch factual errors, weak interpretations, and generated text that may remain too close to the wording of the original source.

External integrations behind the Worker#

Credentials for Kit and other providers are never exposed to the browser.

The client communicates with the Worker.

The Worker validates the request, performs the third-party operation, and returns only the information the client needs.

This keeps provider credentials and implementation details inside the trusted backend boundary.

Result#

The project evolved into a platform capable of:

  • Generating bilingual publications from Markdown.
  • Building complete localized pages with canonical URLs and hreflang.
  • Keeping the CMS separate from the public website.
  • Managing drafts, published content, and scheduled publications.
  • Retrieving and evaluating technology news through an AI-assisted pipeline.
  • Requiring human approval before publication.
  • Managing editorial images and social metadata.
  • Registering newsletter subscribers through an API with explicit consent.
  • Integrating with Kit for double opt-in and campaign delivery.
  • Deploying both content and APIs through Cloudflare infrastructure.
  • Verifying API contracts, security controls, and content generation through automated tests.

The generator currently produces 18 localized article pages from 18 Markdown source files across Spanish and English content.

Current limitations#

Bodan the Backend is still evolving.

The main areas that remain unfinished or need further work are:

  • Completing the production rollout of the Kit integration.
  • Synchronizing all subscriber state changes between Kit and D1 through webhooks.
  • Adding browser-based end-to-end tests.
  • Splitting several modules that still carry too many responsibilities.
  • Publishing the first reproducible experiments in the Lab.
  • Adding real usage and performance metrics once the platform receives enough traffic to make those metrics meaningful.

I keep these limitations visible because the purpose of the project is not to present a fictional perfect architecture.

The goal is to document real engineering decisions, the tradeoffs behind them, and how the system changes as requirements become clearer.

Lessons learned#

The main lesson from the project is that a backend does not require every piece of application content to become dynamic.

Static content and backend engineering are not mutually exclusive.

By separating static publications from runtime operations, I was able to keep the public-facing system fast and resilient while still introducing a CMS, APIs, editorial automation, AI-assisted workflows, and newsletter infrastructure.

The project also reinforced another principle: the most useful automation is not necessarily the one that removes every human decision.

The better automation is often the one that removes mechanical work while making the remaining decision points explicit.

In this system, AI reduces the cost of discovery, filtering, and drafting.

Human judgment remains responsible for publication.