A browser showing an error does not tell you which part of an API failed. Curl makes the request explicit: the method, destination, headers and body become things you can inspect and repeat. This command sheet uses a small local fixture, so practising POST requests does not create records in a real application.
You need Node.js 20 or later and curl 7.76 or later. Use a currently supported Node release for production; the version floor here describes the fixture APIs. Check node --version and curl --version first.
Start a private practice server
Download and extract the example into a new folder. It has no third-party dependencies:
curl-api-command-sheet/
├── README.md
├── server.mjs
└── test.mjsIn the extracted folder, run:
node --test test.mjs
node server.mjsThe server binds only to 127.0.0.1:4010. Keep that terminal open and run requests from a second terminal on the same computer. It returns synthetic data and stores nothing. Do not deploy it publicly; its demonstration token is deliberately known.
Separate status, headers and body
curl --include --connect-timeout 2 --max-time 5 \
http://127.0.0.1:4010/healthExpect status 200, a JSON content type, and {"ok":true}. --include prints response headers with the body. The connection timeout bounds establishing the connection; the overall timeout bounds the transfer.
A HEAD request asks for headers without a response body:
curl --head --max-time 5 http://127.0.0.1:4010/healthHEAD is useful, but a real application may implement it differently or incorrectly. A successful HEAD alone does not prove the GET body is correct. HTTP methods describe semantics rather than a promise that every server implements every method. See HTTP semantics.
Make failures visible to scripts
By default, receiving an HTTP error response can still count as a successful curl transfer. Use this for health checks:
curl --silent --show-error --fail-with-body --max-time 5 \
http://127.0.0.1:4010/missing
printf 'curl exit code: %s\n' "$?"The fixture returns a 404 JSON error and curl exits with code 22. A connection failure or timeout has a different exit code. Capture $? immediately; running another command first replaces it. --fail-with-body retains the error body, which is useful for debugging but can contain sensitive information in a real API. Its behaviour and version requirement are documented in the curl manual.
For an intentional negative test, that nonzero exit is the expected result, not a reason to change the server to return 200 for errors.
Send JSON and check the contract
curl --include --max-time 5 \
--header 'Content-Type: application/json' \
--data '{"title":"My first test"}' \
http://127.0.0.1:4010/notes--data selects POST here. The fixture should return 201 and stored:false: this is a response-shape exercise, not persistence.
Repeat with an empty title, malformed JSON, a missing content-type header, and GET instead of POST. Expected statuses are 422, 400, 415 and 405 respectively. The method error also includes an Allow header. For your own API, write down its documented contract rather than assuming these particular choices are universal.
Never point a copied POST or DELETE example at production without understanding its side effects. Automatic retries can duplicate work unless the server implements a reliable idempotency mechanism.
Practise authentication without exposing real tokens
The fixture's public teaching token is safe to use literally:
curl --include --max-time 5 http://127.0.0.1:4010/private
curl --include --max-time 5 \
--header 'Authorization: Bearer local-lab-token' \
http://127.0.0.1:4010/privateExpect 401 without it and 200 with it. This demonstrates request structure, not production authentication.
For real credentials, do not paste tokens into shell history, URLs, screenshots or bug reports. A restricted header file prepared through your approved secret workflow can be read with --header @/private/path/request-headers.txt; protect it and remove it according to your retention policy. Curl verbose traces and saved response headers can expose authorization material, cookies or personal information. Redact copies before sharing and retain originals only where needed.
Move from the lab to your own application
Use HTTPS with normal certificate verification. Do not use --insecure as a permanent workaround. Test the app's loopback health endpoint on the VPS, then its public HTTPS route from your laptop; this separates backend availability from DNS and reverse-proxy behaviour.
Check expected success, missing credentials, invalid input, unavailable dependencies and timeouts. Record status and safe response fields. Avoid treating an HTML login page returned with status 200 as an API health success.
The included tests exercise the local fixture and actual curl error exits. They do not validate your production authentication, database, TLS setup or browser CORS policy. Stop terminal A with Ctrl-C when finished. Continue with Nginx failure diagnosis or the production learning path.
