Recommended resources
The freely available resources this lesson will direct you to.
Hello. Your repository already starts an HTTP service and keeps startup separate from route handling. The next step is to make that service do one complete piece of booking work: accept a booking, retain it in memory, and return it by ID. Use the API contract and assumptions you wrote earlier as the source of truth; this lesson supplies implementation decisions to examine, not a replacement contract or code to copy.
Budget about 45–55 minutes. Work from your existing repository, use documentation for exact Go APIs, and write no AI-generated implementation code. The evidence you want at the end is a live create request followed by a successful get request—not merely a service that compiles.
Attempt the vertical slice before studying an example
Give yourself 15 minutes with the editor first. Extend the existing service with the create and get routes in your contract. If your contract uses conventional paths, these might be POST /bookings and GET /bookings/{id}. Aim for four observable outcomes: a successful create, a successful get of that ID, a missing ID, and a malformed create request.
Before coding, settle three details your contract may have left open: which fields the client supplies, whether the server assigns the ID, and which response identifies the newly created booking. Record any assumption you make. If the attempt stalls, identify the exact boundary: route matching, JSON decoding, ID assignment, storage, or response writing. Then use the material below to resolve that obstacle. Looking up a function signature is allowed; finding a finished booking handler to adapt defeats this attempt.
A vertical slice is deliberately narrower than a booking platform. You do not need a database, availability search, overlap protection, status transitions, or notifications to prove create-and-get works.
Trace one request through the existing service
The short standard-library example in Building a RESTful API in Go Using Only the Standard Library (Episode 1), from Go In 5 Minutes, is useful for seeing how a server, routes, handlers, and a shared in-memory store meet. Its API is a generic key-value service, not a booking implementation to copy.
Building a RESTful API in Go Using Only the Standard Library (Episode 1)
Watch the server components and the live request sequence to sharpen your mental model of a vertical slice.
Watch server components for the distinction between routing and handling, then store wiring for how one in-memory store reaches the handlers. In live requests, notice that a lookup fails before data is written and succeeds afterward. Your booking API will use your own routes, methods, and response contract.
Keep your dependency direction from the previous lesson. One small extension is enough: main creates one in-memory booking store and supplies it to the HTTP handler; the handler translates requests and responses; booking data and storage behavior live outside httpapi. If you add internal/booking, it should not import net/http. You do not need to introduce service and repository interfaces simply to make the directory structure look complete.
“One store” matters. Constructing a new map inside each handler invocation would make a successful create disappear before the following get. A package-global map might appear to fix that, but explicitly passing the store makes its lifetime and ownership visible.
For the store itself, decide on a booking value, an ID-keyed map, and an ID generator. Server-generated, process-local sequential IDs are sufficient if they agree with your contract; they are not a promise of uniqueness across restarts or multiple processes. Protect both ID assignment and map access with synchronization. HTTP handlers can run concurrently, and an unprotected Go map is not a safe shared store. A mutex around writes and a read lock around lookups is one straightforward choice. Returning booking values rather than mutable pointers also makes it harder for a caller to change stored state without holding the lock.
Connect the HTTP contract to Go’s APIs
For Go 1.22 or later, the standard ServeMux can match an HTTP method and capture one path segment. Read the Enhancements section of Go’s Routing Enhancements for Go 1.22 before choosing your get route.
Routing Enhancements for Go 1.22
Read the Go team’s explanation of method patterns and path values so the get handler receives an ID without manual path slicing.
In “Enhancements,” begin with the paragraph starting “In Go 1.22, the existing code will continue to work” and read method routing through the explanation of the 405 Method Not Allowed response. Focus on the {id} segment and Request.PathValue. Stop before the paragraph about wildcards matching remaining path segments; you do not need that form here.
Check your installed Go version before relying on that syntax. A method-qualified GET route also matches HEAD, so do not assume it means “GET and no other method” when inspecting behavior. Register only the paths you intend; the /healthz route from the previous lesson should remain available.
For the JSON boundary, the official encoding/json reference covers what you need now: exported fields and JSON tags, decoding a request body, and encoding a response.
Use the Go package documentation as an API reference while implementing your own request and response types.
Under “Marshal,” read struct field rules for exported fields and JSON tags. Under “Types,” find “Decoder,” then “NewDecoder” and “Decode”; read decoding APIs, skipping the intervening “Buffered” detail. Finally, under “Encoder,” find “NewEncoder” and “Encode” and read response APIs. Focus on what each call accepts and returns, rather than adapting an example program.
Give the handler a request type containing only client-supplied fields. Do not let a client choose a server-generated ID by reusing the stored booking type without thought. Decode first, reject malformed JSON and missing fields required by your contract, and only then insert. A single decoder call is not, by itself, a guarantee that unknown fields or a second JSON value will be rejected. Do not describe this endpoint as strictly decoded unless you actually implement and verify that behavior; strict request handling has its own hardening lesson.
On success, return the created booking in the shape your contract specifies. If you chose 201 Created, include the ID in the response and, if your contract calls for it, a Location header pointing to its get path. Set the JSON content type before writing the response status or body. A get of an unknown ID should produce the contract’s not-found response rather than an empty booking that looks successful.
Verify behavior, then inspect what the behavior proves
Build and run the service using the commands established last lesson. Use a create request shaped to your contract. For illustration only, if you chose the field names below and RFC 3339 timestamps, a manual probe could be:
curl -i -X POST http://127.0.0.1:8080/bookings \
-H 'Content-Type: application/json' \
-d '{"customer_id":"c1","resource_id":"r1","start_at":"2026-08-12T09:00:00Z","end_at":"2026-08-12T10:00:00Z"}'
Copy the returned ID into a get request. Check the response body against the create request and your contract, not just the status code. Then request an ID you have not created, send malformed JSON to the create route, and revisit /healthz. If you restart the process, expect the in-memory booking to disappear; persistence is outside this slice.
A live create-and-get establishes that the routes, JSON boundary, shared store, and response path work together. It does not establish concurrency safety by observation alone. Inspect every map read and write and the ID-generation operation: are they protected by the same store’s synchronization, and is that store constructed only once for the running service? Also inspect the imports. Booking storage should not depend on HTTP merely because HTTP is its first caller.
Keep a brief implementation note: what you built unaided, which exact APIs required documentation, what remains an assumption, and which failure you had to debug. That record will be useful when you later assess implementation fluency against code evidence.
You now have the smallest useful booking feature: a client can create a booking and retrieve that same booking by ID while the process is running. The important decisions are not the number of files or layers; they are that the store has one clear lifetime, shared state is protected, and HTTP behavior matches your stated contract. Next, you will add focused tests for the successful path and a failure case so this behavior can be checked without manually driving a live server.