Computing Foundations › APIs and the Web › Day 23
Hands-on lab — Day 23: REST Fundamentals: Resources and Verbs
- ← Back to the Day 23 lesson
- Open the hands-on files on GitHub — clone or download them from the public labs repository
- Local path in your clone:
labs/sections/computing-foundations/day-023-rest-fundamentals-resources-and-verbs/
Commands
Setup
cd labs/sections/computing-foundations/day-023-rest-fundamentals-resources-and-verbs Run
bash examples/rest_crud.sh
bash starter/rest_crud.sh Test
bash tests/run_tests.sh File tree
examples/rest_crud.sh expected-output/sample-run.txt metadata.yml README.md requirements/README.md security.md starter/rest_crud.sh starter/rest-worksheet.md tests/run_tests.sh troubleshooting.md
Lab README
Day 023 lab — CRUD Against a REST API
Lesson
- Lesson title: REST Fundamentals: Resources and Verbs
- Day number: 23 of 365
- Lesson article: https://ai-roadmap-365.github.io/day-023-rest-fundamentals-resources-and-verbs
- Lab files: everything you need is in this directory — follow “How to run” below.
- Browse the course locally: from the repository root, this lab also appears in the course website at
/labs/day-023-rest-fundamentals-resources-and-verbswhen the site is running.
Purpose
Day 23's lesson built the resource-and-verb model of REST. This lab puts it in your hands: you run a full create-read-update-delete cycle against a real public REST API, using nothing but the HTTP verbs and two resource addresses. You read a representation of an item, list a collection, create a new item and watch the server assign its id, replace an item, and delete one — reading the real status code the server returns for each verb.
Learning objectives
- Read a resource's JSON representation with
GETon an item and a collection. - Create a resource by
POSTing to the collection and confirm a201 Created. - Replace a resource with
PUTand read the200 OKresponse. - Delete a resource with
DELETEand read its status code. - Explain why
POSTtargets the collection whileGET/PUT/DELETEtarget an item, and why a practice API's faked writes do not persist.
Prerequisites
- The Day 23 lesson, Day 18 (HTTP methods), and Day 22 (what an API is).
- A terminal with
curland network access.python3is optional (it only pretty-prints the JSON; the lab works without it).
Supported operating systems
- macOS and Linux — fully supported (
curlpreinstalled). - Windows —
curlships with Windows 10+; or run under WSL.
Hardware requirements
Any computer with an internet connection. The lab only makes ordinary web requests.
Required software
curl only — preinstalled everywhere. python3 is optional. See
requirements/README.md.
Free and open-source options
Everything here is free: curl is open source, and the test service
(jsonplaceholder.typicode.com) is a free public REST API that needs no
account or key.
Installation
None. Change into this directory:
cd labs/sections/computing-foundations/day-023-rest-fundamentals-resources-and-verbs
File structure
day-023-.../
├── README.md
├── metadata.yml
├── starter/
│ ├── rest_crud.sh ← YOUR working file (4 exercises)
│ └── rest-worksheet.md ← record your status codes and the new id
├── examples/
│ └── rest_crud.sh ← completed reference implementation
├── tests/
│ └── run_tests.sh
├── expected-output/
│ └── sample-run.txt ← real captured run
├── requirements/README.md
├── troubleshooting.md
└── security.md
How to run
## 1. See the finished CRUD cycle first (needs network)
bash examples/rest_crud.sh
## 2. Complete the four exercises in the starter, then run it
bash starter/rest_crud.sh
## 3. Check your work
bash tests/run_tests.sh
What the commands do
bash examples/rest_crud.sh— runs the five REST exchanges againstjsonplaceholder.typicode.com:GET /posts/1(read an item),GET /posts(list the collection),POST /posts(create — returns201and a new id),PUT /posts/1(replace — returns200), andDELETE /posts/1(remove — returns200). It pretty-prints JSON withpython3 -m json.toolwhen available and degrades gracefully offline.bash starter/rest_crud.sh— the same skeleton with four numbered exercises; each names the exactcurlcommand to use for GET, POST, PUT, and DELETE.bash tests/run_tests.sh— always verifies structure (files present, both scripts parse, the example exercises each verb, the starter names four exercises), then — when online — checks the real behaviour:GET /posts/1returns200with the expected keys andPOST /postsreturns201. Offline, the live checks are skipped (never failed). Exits 0 on success.
Expected output
See expected-output/sample-run.txt for a
real captured run. The GET bodies are fixed by the service; the created id
is always 101 on JSONPlaceholder.
Validation steps
bash examples/rest_crud.shprints all five sections without errors.- The
POSTsection showsHTTP 201and a body containing"id": 101. - The
PUTandDELETEsections showHTTP 200. - The tests pass.
Tests
bash tests/run_tests.sh
Expected final line: 15 checks, 0 failure(s), 0 skip(s). online (the three
live checks skip, never fail, when offline). Exit 0 on success.
Cleanup
Nothing to clean up — the scripts only make web requests and write no files
(and JSONPlaceholder fakes its writes, so nothing persists server-side).
Reset your edited starter with git checkout -- starter/rest_crud.sh.
Troubleshooting
See troubleshooting.md — faked writes not persisting,
missing python3, status codes stuck to the body, and offline behaviour.
Security notes
See security.md. Only a public test API; never send real secrets to a practice service; keep tokens in headers, never in URLs.
Extension exercises
- Send a
PATCH /posts/1with only{"title":"just the title"}and compare the returned representation to what a fullPUTreturned. - Fetch a nested collection —
GET /posts/1/commentsandGET /users/1/todos— and note how the URL reads as a path through related resources. - Run the same
DELETE /posts/1twice and confirm the outcome is identical (idempotency), then explain whyPOST /poststwice would differ.
Navigation
- Previous day: Day 22 — What an API Is and Why Everything Has One.
- Next day: Day 24 — JSON and Data Serialization.
Expected output
sample-run.txt
Day 023 — CRUD Against a REST API
Target: https://jsonplaceholder.typicode.com (a free public REST API that fakes writes)
=== READ one item — GET /posts/1 ===
$ curl -s https://jsonplaceholder.typicode.com/posts/1 | python3 -m json.tool
{
"userId": 1,
"id": 1,
"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
"body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}
=== READ the collection — GET /posts (first entries) ===
$ curl -s https://jsonplaceholder.typicode.com/posts | python3 -m json.tool | head -20
[
{
"userId": 1,
"id": 1,
"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
"body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
},
{
"userId": 1,
"id": 2,
"title": "qui est esse",
"body": "est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla"
},
{
"userId": 1,
"id": 3,
"title": "ea molestias quasi exercitationem repellat qui ipsa sit aut",
"body": "et iusto sed quo iure\nvoluptatem occaecati omnis eligendi aut ad\nvoluptatem doloribus vel accusantium quis pariatur\nmolestiae porro eius odio et labore et velit aut"
},
{
... collection contains 100 posts total.
=== CREATE — POST /posts (returns 201 + the created resource) ===
$ curl -s -X POST -H 'Content-Type: application/json' -d '{...}' https://jsonplaceholder.typicode.com/posts
{
"title": "my new post",
"body": "written today",
"userId": 1,
"id": 101
}
status: HTTP 201 (expect 201 Created — the server assigned the new id)
=== UPDATE — PUT /posts/1 (replace the whole item) ===
$ curl -s -X PUT -H 'Content-Type: application/json' -d '{...}' https://jsonplaceholder.typicode.com/posts/1
{
"id": 1,
"title": "edited title",
"body": "edited body",
"userId": 1
}
status: HTTP 200 (expect 200 OK — the replaced representation)
=== DELETE — DELETE /posts/1 (remove the item) ===
$ curl -s -X DELETE https://jsonplaceholder.typicode.com/posts/1 -w 'HTTP %{http_code}'
status: HTTP 200 (expect 200 — an empty body; DELETE is idempotent)
Done. Two addresses (/posts and /posts/1) and five verbs drove the whole
lifecycle. Note: JSONPlaceholder fakes writes, so the created id 101 is
not re-fetchable — the status codes are real, the persistence is simulated.
Source files
examples/rest_crud.sh (5633 bytes)
#!/usr/bin/env bash
# Day 023 lab — CRUD Against a REST API (completed reference implementation).
#
# Runs a full create-read-update-delete cycle against JSONPlaceholder
# (https://jsonplaceholder.typicode.com), a free public REST API that accepts
# CRUD requests and responds realistically. It *fakes* writes: a POST returns
# 201 and a new id, but nothing is actually stored — perfect for safe practice.
#
# Each section is one HTTP verb acting on a resource address, and prints the
# status code and the JSON body the server returned.
#
# Run from the lab directory: bash examples/rest_crud.sh
#
# Requires network access. If the network (or the test server) is unreachable,
# each section degrades gracefully with a clear message and the script still
# exits 0, so it is safe to run offline.
set -u
API="https://jsonplaceholder.typicode.com"
rule() { printf '\n=== %s ===\n' "$1"; }
# Pretty-print JSON on stdin if python3 is available; otherwise pass it through.
pretty() {
if command -v python3 >/dev/null 2>&1; then
python3 -m json.tool 2>/dev/null || cat
else
cat
fi
}
# Return 0 if we appear to have network access to the API, else 1.
have_network() {
curl -s -o /dev/null --max-time 12 "${API}/posts/1" 2>/dev/null
}
# ---------------------------------------------------------------------------
# READ (GET) one item: fetch a representation of post 1 by its address.
# ---------------------------------------------------------------------------
read_item() {
rule "READ one item — GET /posts/1"
echo "\$ curl -s ${API}/posts/1 | python3 -m json.tool"
curl -s --max-time 20 "${API}/posts/1" | pretty \
|| echo "(could not reach ${API})"
}
# ---------------------------------------------------------------------------
# READ (GET) the collection: list posts (show the first few for brevity).
# ---------------------------------------------------------------------------
read_collection() {
rule "READ the collection — GET /posts (first entries)"
echo "\$ curl -s ${API}/posts | python3 -m json.tool | head -20"
curl -s --max-time 20 "${API}/posts" | pretty | head -20 \
|| echo "(could not reach ${API})"
# Report the total count so the learner sees it is a full collection.
local count
count="$(curl -s --max-time 20 "${API}/posts" \
| grep -o '"id"' | wc -l | tr -d ' ')" || count="?"
echo "... collection contains ${count} posts total."
}
# ---------------------------------------------------------------------------
# CREATE (POST) to the collection: the server assigns a new id, returns 201.
# ---------------------------------------------------------------------------
create_item() {
rule "CREATE — POST /posts (returns 201 + the created resource)"
echo "\$ curl -s -X POST -H 'Content-Type: application/json' -d '{...}' ${API}/posts"
local body code
body="$(curl -s --max-time 20 -X POST \
-H "Content-Type: application/json" \
-d '{"title":"my new post","body":"written today","userId":1}' \
"${API}/posts")" || { echo "(could not reach ${API})"; return; }
code="$(curl -s -o /dev/null --max-time 20 -X POST \
-H "Content-Type: application/json" \
-d '{"title":"my new post","body":"written today","userId":1}' \
-w '%{http_code}' "${API}/posts")" || code="000"
printf '%s' "${body}" | pretty
echo "status: HTTP ${code} (expect 201 Created — the server assigned the new id)"
}
# ---------------------------------------------------------------------------
# UPDATE (PUT) an item: replace post 1 entirely with the body we send.
# ---------------------------------------------------------------------------
update_item() {
rule "UPDATE — PUT /posts/1 (replace the whole item)"
echo "\$ curl -s -X PUT -H 'Content-Type: application/json' -d '{...}' ${API}/posts/1"
local body code
body="$(curl -s --max-time 20 -X PUT \
-H "Content-Type: application/json" \
-d '{"id":1,"title":"edited title","body":"edited body","userId":1}' \
"${API}/posts/1")" || { echo "(could not reach ${API})"; return; }
code="$(curl -s -o /dev/null --max-time 20 -X PUT \
-H "Content-Type: application/json" \
-d '{"id":1,"title":"edited title","body":"edited body","userId":1}' \
-w '%{http_code}' "${API}/posts/1")" || code="000"
printf '%s' "${body}" | pretty
echo "status: HTTP ${code} (expect 200 OK — the replaced representation)"
}
# ---------------------------------------------------------------------------
# DELETE an item: remove post 1. Idempotent — repeating it is harmless.
# ---------------------------------------------------------------------------
delete_item() {
rule "DELETE — DELETE /posts/1 (remove the item)"
echo "\$ curl -s -X DELETE ${API}/posts/1 -w 'HTTP %{http_code}'"
local code
code="$(curl -s -o /dev/null --max-time 20 -X DELETE \
-w '%{http_code}' "${API}/posts/1")" || code="000"
echo "status: HTTP ${code} (expect 200 — an empty body; DELETE is idempotent)"
}
main() {
echo "Day 023 — CRUD Against a REST API"
echo "Target: ${API} (a free public REST API that fakes writes)"
if ! have_network; then
echo
echo "OFFLINE: could not reach ${API}. The commands below need network"
echo "access. Connect to the internet and re-run: bash examples/rest_crud.sh"
exit 0
fi
read_item
read_collection
create_item
update_item
delete_item
echo
echo "Done. Two addresses (/posts and /posts/1) and five verbs drove the whole"
echo "lifecycle. Note: JSONPlaceholder fakes writes, so the created id 101 is"
echo "not re-fetchable — the status codes are real, the persistence is simulated."
}
main "$@"
metadata.yml (602 bytes)
lesson_id: D023
day: 23
kind: api-example
languages: [bash]
setup_commands:
- cd labs/sections/computing-foundations/day-023-rest-fundamentals-resources-and-verbs
run_commands:
- bash examples/rest_crud.sh
- bash starter/rest_crud.sh
test_commands:
- bash tests/run_tests.sh
cleanup_commands:
- 'git checkout -- starter/rest_crud.sh starter/rest-worksheet.md # optional: reset your work'
requires_network: true
requires_api_key: false
estimated_minutes: 30
last_executed: '2026-07-12'
executed_on: 'macOS (curl 8.7.1), online, bash tests/run_tests.sh → 15 checks, 0 failure(s), 0 skip(s)'
requirements/README.md (881 bytes)
# Dependencies — Day 023 lab
**`curl` only** — preinstalled on macOS and mainstream Linux (and available on
Windows 10+ / WSL). `python3` is optional: it only pretty-prints the JSON with
`python3 -m json.tool`, and the scripts fall back to raw output when it is
absent. No package installs, no accounts, no API keys.
The lab reaches one free public REST API — `https://jsonplaceholder.typicode.com`
— so it needs network access to show live output. With no network it degrades
gracefully: the scripts print a clear message and the tests skip (never fail)
the live checks. No sudo required.
Note that JSONPlaceholder **fakes** its writes: a `POST`, `PUT`, or `DELETE`
returns a realistic status code and body, but nothing is stored server-side.
That is intentional and safe for practice — you can run the CRUD cycle as many
times as you like without changing anything.
starter/rest_crud.sh (1957 bytes)
#!/usr/bin/env bash
# Day 023 starter — CRUD Against a REST API.
# Complete the four exercises below. Each names the exact curl command to use.
# Run from the lab directory: bash starter/rest_crud.sh
# The completed reference is in ../examples/rest_crud.sh — try it yourself first.
set -u
API="https://jsonplaceholder.typicode.com"
rule() { printf '\n=== %s ===\n' "$1"; }
echo "Day 023 starter — CRUD Against a REST API"
echo "Target: ${API}"
# Exercise 1 (READ): fetch one item and pretty-print its JSON representation.
# Replace the echo with:
# curl -s "$API/posts/1" | python3 -m json.tool
rule "1. READ — GET /posts/1"
echo "EXERCISE — your turn: GET $API/posts/1 and pretty-print the JSON body"
# Exercise 2 (CREATE): POST a new post to the COLLECTION and print the status.
# Replace with:
# curl -s -w '\nHTTP %{http_code}\n' -X POST \
# -H "Content-Type: application/json" \
# -d '{"title":"my post","body":"hello","userId":1}' \
# "$API/posts"
rule "2. CREATE — POST /posts"
echo "EXERCISE — your turn: POST a JSON body to $API/posts and confirm HTTP 201"
# Exercise 3 (UPDATE): PUT a full replacement to the ITEM and print the status.
# Replace with:
# curl -s -w '\nHTTP %{http_code}\n' -X PUT \
# -H "Content-Type: application/json" \
# -d '{"id":1,"title":"edited","body":"changed","userId":1}' \
# "$API/posts/1"
rule "3. UPDATE — PUT /posts/1"
echo "EXERCISE — your turn: PUT a full new representation to $API/posts/1 (HTTP 200)"
# Exercise 4 (DELETE): DELETE the ITEM and print the status code.
# Replace with:
# curl -s -w '\nHTTP %{http_code}\n' -X DELETE "$API/posts/1"
rule "4. DELETE — DELETE /posts/1"
echo "EXERCISE — your turn: DELETE $API/posts/1 and read the status code (HTTP 200)"
echo
echo "When all four are done, compare your output with ../examples/rest_crud.sh"
echo "Remember: POST targets the COLLECTION (/posts); GET, PUT, DELETE target an ITEM (/posts/1)."
starter/rest-worksheet.md (1375 bytes)
# REST worksheet — Day 023
Run the commands (from the examples or your completed starter) against
JSONPlaceholder (`https://jsonplaceholder.typicode.com`) and record what the
server returned. The status codes are real even though the writes are faked.
## 1. Read — status code for a GET
- Command: `curl -s -o /dev/null -w '%{http_code}\n' https://jsonplaceholder.typicode.com/posts/1`
- Status code returned: `____` (expect 200)
## 2. Create — status code and assigned id for a POST
- Command:
`curl -s -w '\nHTTP %{http_code}\n' -X POST -H "Content-Type: application/json" -d '{"title":"my post","body":"hello","userId":1}' https://jsonplaceholder.typicode.com/posts`
- Status code returned: `____` (expect 201)
- The `id` JSONPlaceholder assigned to your new post: `____`
## 3. Delete — status code for a DELETE
- Command: `curl -s -o /dev/null -w '%{http_code}\n' -X DELETE https://jsonplaceholder.typicode.com/posts/1`
- Status code returned: `____` (expect 200)
## 4. Collection vs item
- Which of your four requests targeted the **collection** (`/posts`)? `____`
- Which targeted an **item** (`/posts/1`)? `____`
## 5. One paragraph
Explain, in your own words (4–6 sentences), why you can safely retry a failed
`GET` or `DELETE` but must be careful retrying a failed `POST`. Name the
property that makes the difference.
> _your answer here_
tests/run_tests.sh (4220 bytes)
#!/usr/bin/env bash
# Tests for the Day 023 lab. Run from the lab directory:
# bash tests/run_tests.sh
#
# Two kinds of checks:
# * Structure checks always run and must pass (files present, both scripts
# parse, the example exercises each CRUD verb, the starter names its four
# exercises).
# * Network checks run only when the API is reachable. They verify that a
# GET returns 200 with the expected keys and that a POST returns 201.
# Offline (or when the public server is transiently unavailable) the live
# checks are SKIPPED with a message rather than failed — a third-party
# outage is not the learner's bug.
#
# The script exits 0 when no structure check failed, whether online or offline.
set -u
lab_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
API="https://jsonplaceholder.typicode.com"
failures=0
checks=0
skips=0
pass() { checks=$((checks + 1)); echo " ok: $1"; }
fail() { checks=$((checks + 1)); failures=$((failures + 1)); echo " FAIL: $1"; }
skip() { skips=$((skips + 1)); echo " skip: $1"; }
echo "== Structure checks =="
example="${lab_dir}/examples/rest_crud.sh"
starter="${lab_dir}/starter/rest_crud.sh"
worksheet="${lab_dir}/starter/rest-worksheet.md"
[ -s "${example}" ] && pass "example script exists" || fail "example script missing"
[ -s "${starter}" ] && pass "starter script exists" || fail "starter script missing"
[ -s "${worksheet}" ] && pass "worksheet exists" || fail "worksheet missing"
# The example must exercise every CRUD verb and the pretty-printer.
for needle in "GET /posts/1" "POST /posts" "PUT /posts/1" "DELETE /posts/1" "json.tool"; do
if grep -q -- "${needle}" "${example}"; then pass "example uses '${needle}'"; else fail "example missing '${needle}'"; fi
done
# The starter must name its four numbered exercises.
ex_count="$(grep -cE 'Exercise [1-4]' "${starter}" 2>/dev/null || true)"
if [ "${ex_count:-0}" -ge 4 ]; then pass "starter has 4 numbered exercises"; else fail "starter has 4 numbered exercises (found ${ex_count:-0})"; fi
# Both scripts must be valid bash.
if bash -n "${example}" 2>/dev/null; then pass "example has valid bash syntax"; else fail "example has a syntax error"; fi
if bash -n "${starter}" 2>/dev/null; then pass "starter has valid bash syntax"; else fail "starter has a syntax error"; fi
echo
echo "== Network checks =="
if ! curl -s -o /dev/null --max-time 12 "${API}/posts/1" 2>/dev/null; then
skip "no network access — skipping all live-request checks (expected offline)"
else
pass "network reachable (${API} responded)"
# Check 1: GET /posts/1 returns 200.
code="$(curl -s -o /dev/null --max-time 20 --retry 3 --retry-delay 1 \
-w '%{http_code}' "${API}/posts/1" 2>/dev/null)" || code="000"
case "${code}" in
200) pass "GET /posts/1 returned HTTP 200" ;;
5* | 000) skip "GET check — server transiently unavailable (got '${code}'); retry later" ;;
*) fail "GET /posts/1 should return HTTP 200 (got '${code}')" ;;
esac
# Check 2: GET /posts/1 body carries the expected resource keys.
body="$(curl -s --max-time 20 --retry 3 --retry-delay 1 "${API}/posts/1" 2>/dev/null)" || body=""
if [ -z "${body}" ]; then
skip "GET-keys check — server transiently unavailable; retry later"
elif printf '%s' "${body}" | grep -q '"id"' \
&& printf '%s' "${body}" | grep -q '"title"' \
&& printf '%s' "${body}" | grep -q '"userId"'; then
pass "GET /posts/1 body has the expected keys (id, title, userId)"
else
fail "GET /posts/1 body should contain id, title, and userId"
fi
# Check 3: POST /posts returns 201 (create against the collection).
pcode="$(curl -s -o /dev/null --max-time 20 --retry 3 --retry-delay 1 \
-X POST -H "Content-Type: application/json" \
-d '{"title":"t","body":"b","userId":1}' \
-w '%{http_code}' "${API}/posts" 2>/dev/null)" || pcode="000"
case "${pcode}" in
201) pass "POST /posts returned HTTP 201 Created" ;;
5* | 000) skip "POST check — server transiently unavailable (got '${pcode}'); retry later" ;;
*) fail "POST /posts should return HTTP 201 (got '${pcode}')" ;;
esac
fi
echo
echo "${checks} checks, ${failures} failure(s), ${skips} skip(s)."
[ "${failures}" -eq 0 ]
Troubleshooting
Troubleshooting — Day 023 lab
My POST returned 201 but the new post is not there when I GET it
This is expected. JSONPlaceholder fakes writes. It responds as if it
created the resource — a real 201 Created and the id 101 — but nothing is
actually stored. If you then GET /posts/101, you get a 404, because the
post was never persisted. The status codes and echoed bodies are genuine; only
the persistence is simulated. This is exactly what makes the service safe for
practice: you can run the full create-update-delete cycle repeatedly without
changing any real data.
python3: command not found
The pretty-printer is optional. Use python instead of python3, or drop the
pipe entirely — curl -s .../posts/1 still returns valid JSON, just
unformatted. The example script already falls back to raw output when
python3 is missing.
The status code prints stuck to the JSON body
Keep the newline in the -w format: -w "\nHTTP %{http_code}\n". Without the
leading \n, the status code lands on the same line as the last byte of the
body.
My POST behaved like a GET (no creation, no 201)
You dropped -X POST or the -d body. The -d flag supplies the request body
and, on its own, already switches the method to POST; combine it with a
Content-Type: application/json header so the server parses your JSON.
I created to /posts/1 instead of /posts and got a surprise
You create by POSTing to the collection (/posts), not to an item
(/posts/1) — the item does not exist yet, and the server assigns the new id.
GET, PUT, PATCH, and DELETE target a specific item you already know.
jsonplaceholder.typicode.com is slow or returns a 5xx
It is a shared free service and can occasionally be busy. The example and the tests retry transient errors and, in the tests, skip (never fail) a live check when the server is transiently unavailable. Wait a moment and re-run.
Offline
The scripts and tests detect no network and exit 0 with a clear message. Connect to the internet to see live output.
Security notes
Security notes — Day 023 lab
- What it does: sends ordinary REST requests (GET, POST, PUT, DELETE) to
one public test API (
jsonplaceholder.typicode.com) and reads the responses. No data of yours is stored anywhere — the service fakes its writes. No sudo; no files written outside the lab. - Never send real secrets to a practice API. The sample bodies here are
non-sensitive placeholder values (
"title":"my post"). Do not post real API keys, tokens, passwords, or personal data to any public test service. - Keep credentials in headers, never in URLs. A real REST API authenticates
with a token in the
Authorizationheader. URLs appear in logs, browser history, and proxies, so a secret in a path or query string (/posts?token=...) is a leak. This lab uses no credentials at all. - Predictable URLs are enumerable. Because
/posts/1,/posts/2, ... are guessable, a real server must authorize every request against the specific resource. When you build your own API, never assume that knowing a URL implies the right to access it. - Only call APIs you are allowed to. Public test services like this one invite practice traffic; probing or hammering servers you do not own or have permission to test can violate their terms or the law.