Overview
13 Building an API
This chapter shows how to turn a Phoenix app into a clean, reuse-friendly JSON API. Instead of serving HTML to browsers, you design endpoints that return structured JSON for mobile clients and third-party integrations, focusing on clear routing, minimal middleware, and composable rendering that scales from single resources to collections and nested associations.
You begin by scoping API traffic in the router: add an :api pipeline that accepts only JSON and omit browser-oriented plugs like sessions, flash, CSRF protection, and custom authentication (the example API is public and read-only). Under the “/api” scope, route resources such as “/items” to controllers in a dedicated namespace (AuctionWeb.Api.*) to separate concerns from your HTML controllers. The routes expose only index and show.
The API controller is intentionally small: index fetches all items and renders “index.json”; show fetches one item (preloading bids) and renders “show.json.” The real work lives in views, where you override render/2 to return maps that Phoenix converts to JSON. Start by shaping a top-level data key that contains item fields (type, id, title, description, ends_at). Then refactor to make this shape reusable: use render_one/4 to render a single item via an “item.json” function, and render_many/4 to produce the “index.json” collection with the same structure.
Next, enrich the payload with associations. Create a BidView that defines a compact bid representation (type, id, amount). Update ItemView to render “item_with_bids.json,” adding a bids key built with render_many/4. Finally, add a UserView exposing minimal user data (type, id, username) and compose it into each bid with render_one/4. By splitting item, bid, and user rendering into dedicated views, you get small, composable templates that are easy to test and reuse across endpoints.
The outcome is an idiomatic Phoenix API: routing and pipelines keep concerns clean; controllers stay thin; and views encapsulate JSON shapes with render_one/4 and render_many/4 to consistently handle both single resources and collections—plus nested associations—without duplicating code.
13.4 Summary
FAQ
What does the :api pipeline do in Phoenix, and why use it for JSON APIs?
The :api pipeline configures the router to accept "json" requests and omits browser-specific plugs like sessions, flash, CSRF protection, and secure browser headers. This keeps API requests lean and focused on returning JSON without unnecessary browser concerns.
How do I route API requests under /api and point them to namespaced controllers?
Wrap routes in scope "/api", AuctionWeb.Api do and pipe_through :api. Then declare resources like resources "/items", ItemController, only: [:index, :show] so /api/items and /api/items/:id hit AuctionWeb.Api.ItemController.
Why create an AuctionWeb.Api.ItemController separate from the HTML controller?
Separating controllers by namespace (AuctionWeb.Api.* vs AuctionWeb.*) clarifies intent and avoids mixing HTML and JSON concerns. Each controller can render the correct formats and use the appropriate pipelines and views.
What do the index and show actions look like in the API controller?
index fetches items (for example, Auction.list_items()) and renders "index.json" with items: items. show fetches a single item with associated bids (for example, Auction.get_item_with_bids(id)) and renders "show.json" with item: item.
How do Phoenix views render JSON without templates?
Create view render/2 clauses that return plain Elixir maps; Phoenix serializes them to JSON. For example, render("show.json", %{item: item}) returns %{data: ...}, and you skip .eex templates entirely.
When should I use render_one/4 vs render_many/4?
Use render_one/4 to format a single resource with a sub-template (for example, "item.json"). Use render_many/4 to format a collection (for example, a list of items or bids) using the same sub-template for each element.
How do I include bids and bidding user data in the item JSON?
In ItemView, render an "item_with_bids.json" that includes bids: render_many(item.bids, AuctionWeb.Api.BidView, "bid.json"). In BidView, add user: render_one(bid.user, AuctionWeb.Api.UserView, "user.json") to nest minimal user info with each bid.
Do I need sessions, CSRF, or custom authentication plugs in a read-only API?
Not typically. For a public, read-only JSON API, omit browser-specific plugs and custom authentication from the :api pipeline, keeping it to plug :accepts, ["json"].
How can I verify the API responses during development?
Hit endpoints like /api/items and /api/items/:id in your browser or with tools such as curl, HTTPie, Postman, or a JSON-viewing browser extension. Confirm the structure and data match your render functions.
What are good practices for structuring and reusing JSON render logic?
Keep controllers thin and put data shaping in views. Extract per-resource shapes into their own render templates (for example, "item.json", "bid.json", "user.json") and compose them with render_one/4 and render_many/4 for consistency and reuse.