Dry Dock Drills
The core lesson is complete.
Now comes the part where we make sure the ideas actually stick.
These drills are optional, but they are excellent final reps. They focus less on “can we deploy?” and more on:
- can we make a live app easier to understand?
- can we spot bad operational patterns quickly?
- can we add small support-minded improvements without overengineering the whole thing?
Think of these as final dry runs for the habits we want to leave with.
None of these drills are meant to become giant side projects. The point is to strengthen production-awareness instincts with small, deliberate changes.
Drill 1 — Maintenance: Add a Tiny Beacon
Section titled “Drill 1 — Maintenance: Add a Tiny Beacon”Objective: Add the smallest possible operational health signal.
Scenario
Section titled “Scenario”You have an Express application that starts and serves routes, but there is no clean way to ask:
“Are you alive right now?”
What to do
Section titled “What to do”Add a dedicated route at:
/pingIt should return a small JSON response such as:
{ "status": "alive" }Keep it fast and simple.
Expected outcome
Section titled “Expected outcome”You should end up with a small app.get() route that:
- responds immediately
- does not depend on heavy logic
- gives operators a basic liveness signal
Student-to-AI prompt
Section titled “Student-to-AI prompt”I want to add the smallest possible health-style route to an Express app. Show
me the minimal code for a /ping endpoint that returns a simple JSON
response, and explain why this kind of route is useful in a deployed
application.
Drill 2 — Custom Job: Fail Loudly, Not Rudely
Section titled “Drill 2 — Custom Job: Fail Loudly, Not Rudely”Objective: Improve operational visibility around a failing route.
Scenario
Section titled “Scenario”A route catches an error and returns a generic 500 message to the user, but logs nothing useful for the developer or operator.
app.post("/checkout", async (req, res) => { try { await processPayment(req.body); res.status(200).send("Success."); } catch (error) { res.status(500).send("Something went wrong with the payment."); }});What to do
Section titled “What to do”Update the catch block so it:
- logs a clear error message
- includes the underlying
error.message - still returns a safe, generic message to the user
Expected outcome
Section titled “Expected outcome”You should end up with a route that separates:
- operator-facing detail in the logs
- user-facing messaging in the response
That is exactly the habit we want.
Student-to-AI prompt
Section titled “Student-to-AI prompt”I have an Express route with a try/catch block. I want the user to see a safe generic error message, but I want the server logs to include the actual failure details. Show me a clean pattern for doing both at once.
Drill 3 — Solo Special: The Hanging Middleware Trap
Section titled “Drill 3 — Solo Special: The Hanging Middleware Trap”Objective: Diagnose one of the most common Express middleware mistakes.
Scenario
Section titled “Scenario”You add global request logging middleware. The logs look great, but suddenly every request hangs forever.
Here is the code:
app.use((req, res) => { const time = new Date().toISOString(); console.log(`[${time}] Traffic spotted: ${req.method} ${req.url}`);});What to do
Section titled “What to do”Identify what is missing and fix it.
Expected outcome
Section titled “Expected outcome”You should recognize that the middleware intercepted the request but never passed control onward.
The fix is to:
- include
nextin the middleware signature - call
next()after logging
Student-to-AI prompt
Section titled “Student-to-AI prompt”I wrote an Express app.use() middleware to log requests, and now every route
hangs forever. The logging works, but the responses never complete. Help me
spot what I forgot and explain why the whole app appears frozen.
Drill 4 — Add a Better Heartbeat
Section titled “Drill 4 — Add a Better Heartbeat”Objective: Make a health-style endpoint a little more informative without making it heavy.
Scenario
Section titled “Scenario”A route like /health is useful, but { "status": "healthy" } might be a bit too bare.
What to do
Section titled “What to do”Expand your heartbeat route so it safely returns a little more context, such as:
- uptime
- current environment
- timestamp
Keep it lightweight. Do not turn it into a dependency stress test.
Expected outcome
Section titled “Expected outcome”You should end up with a route that is still fast, but more helpful for quick operational sanity checks.
Student-to-AI prompt
Section titled “Student-to-AI prompt”I want my Express /health endpoint to be a little more useful than just { "status": "healthy" }, but I do not want to make it slow or overly complicated. What fields would be reasonable to include?
Drill 5 — Give the App an Identity
Section titled “Drill 5 — Give the App an Identity”Objective: Reduce release confusion after multiple deploys.
Scenario
Section titled “Scenario”You deploy several times in one afternoon. A teammate asks:
“Which version is actually live right now?”
What to do
Section titled “What to do”Add a /version route that returns a small release identifier from an environment variable like:
APP_VERSIONExpected outcome
Section titled “Expected outcome”You should end up with a route that helps answer:
- what version is currently running?
- did the expected release actually make it to the hosted app?
Student-to-AI prompt
Section titled “Student-to-AI prompt”I want my deployed Express app to expose a /version endpoint using an
environment variable like APP_VERSION. Show me a clean way to implement it
and explain why this is useful during rapid deployment cycles.
Drill 6 — Practice Reading the Signal
Section titled “Drill 6 — Practice Reading the Signal”Objective: Learn to interpret what different operational clues actually mean.
Scenario
Section titled “Scenario”Imagine the following observations:
- the startup log appears normally
/healthreturns200 OK- a
POSTrequest shows up in the request logs - but the route still returns a
500
What to do
Section titled “What to do”Explain what those signals tell you, and where you would investigate next.
Expected outcome
Section titled “Expected outcome”You should be able to reason that:
- the app started
- the route is reachable
- the request reached the server
- the failure is likely happening deeper inside route handling or a dependency interaction
This is less about coding and more about thinking like an operator.
Student-to-AI prompt
Section titled “Student-to-AI prompt”My startup logs look healthy, my /health route returns 200, and I can see
the incoming request in the logs, but the user still gets a 500 error. Help
me reason through what that narrows down and what layer I should investigate
next.
Suggested Order
Section titled “Suggested Order”If you want a good progression through the drills, try them in this order:
- Add a tiny beacon
- Fail loudly, not rudely
- Fix the hanging middleware trap
- Improve the heartbeat
- Add the version endpoint
- Practice interpreting the combined signals
That order moves from small implementation changes into stronger operational reasoning.
Final Thought
Section titled “Final Thought”This final lesson was never about turning us into full-time SREs in a single afternoon.
It was about establishing one very practical idea:
once software is live, supportability matters.
These drills reinforce that idea by pushing us to make the application:
- a little clearer
- a little easier to inspect
- a little easier to trust
- and a lot less silent when something goes wrong.
Extra Bits & Bytes
Section titled “Extra Bits & Bytes”Express: Error Handling
⏭ Shipping Manifest
Section titled “⏭ Shipping Manifest”The drills are complete. All that remains is the quick-reference manifest: a compact operational codex for the road.