Leywn (“Last Echo You Will Need”) reaches 1.0.0 today. It is a modern, all-in-one demo and test backend for APIs and HTTP services: a single container that mirrors requests, speaks every common authentication scheme, returns any HTTP status code, injects faults on demand, generates test data, and serves its own Swagger UI — so you can test clients, proxies, load balancers and API gateways without standing up a real backend.
Far more than an echo
Echoing a request is where Leywn starts, not where it stops. /echo returns method, scheme, host, port, path, query parameters, headers, caller IP and body. On top of that sit the endpoints that make it a full test backend:
- Authentication — Basic, API key (with freely chosen header names), JWT Bearer, RFC 8693 token exchange, and real mTLS with a CA, server and client certificate generated fresh at every startup. A proxy mode reads the client certificate from a header when a load balancer terminates TLS.
- Failure on demand —
/status/{code}for any code from 100 to 599,/delay/{ms}for timeouts,/stream/{n}for chunked NDJSON. - Test data — UUIDs, GUIDs, integers in any range, Lorem Ipsum, names, e-mail addresses, colours, dates and times in any IANA timezone, and PNG/JPEG/GIF/SVG/WebP demo images including generated solid-colour images at any size.
- Body tooling — pretty-print JSON, YAML and XML, convert case, Base64/URL/hex/ROT13 encode and decode, decode JWTs, hash with SHA-256 or MD5.
- Content negotiation — every structured endpoint answers in JSON or XML, chosen by the
Acceptheader.
See all endpoints documented at https://leywn.org/docs
Three examples that show what it is for
1. /echo — see exactly what your client sends
Point any HTTP client, SDK or gateway at /echo and it reports back the whole request: the headers your library adds behind your back, how it encoded the body, which IP the server actually saw.
curl -X POST "http://localhost:4000/echo/foo/bar?hello=world" \
-H "Content-Type: application/json" \
-d '{"message": "test"}'
Bodies over the size limit are acknowledged rather than silently dropped (truncated: true), and binary bodies are detected and excluded instead of mangled. Add Accept: application/xml and the same answer arrives as XML — which is also a quick way to check your XML parser against a real payload.
2. /auth/mtls — mutual TLS without building a PKI
Client-certificate authentication is the thing nobody wants to set up just to test it. Leywn generates a CA, a server certificate and a client certificate in memory at every startup, then hands you the client credentials over HTTP:
curl -k https://localhost:4443/auth/mtls/get-client-cert # returns cert_pem + key_pem
curl -k --cert client.pem --key client.key https://localhost:4443/auth/mtls
The response reports the client_dn and client_ca it validated. When a load balancer terminates TLS instead, start Leywn with LEYWN_MTLS_IN_HEADER=X-Client-Cert and it reads the forwarded certificate from that header — so you can prove your Kong, Nginx, ALB or Envoy is passing through the certificate you think it is. Bring your own CA and client certificate via LEYWN_MTLS_CERT / LEYWN_MTLS_KEY and Leywn trusts it automatically.
3. /chaos-engineering — make it fail on purpose
/chaos-engineering answers like /echo, but injects real faults at rates you control: random 4xx/5xx errors, responses truncated mid-stream so the JSON is genuinely unparseable, and random latency up to a bound you set.
# 10 % errors, 10 % mangled responses, 20 % slow up to 2 s
curl http://localhost:4000/chaos-engineering/10/10/20/2000
The same parameters can be sent as X-Chaos-* headers, and every response carries a _chaos field reporting exactly which faults were applied and how much latency was added. That turns “does our retry logic actually work, and does our circuit breaker actually open?” into a question you can answer in one command — against a backend that is honest about what it did to you.
Genuinely lightweight
Leywn is written entirely in Elixir on the Erlang/OTP runtime. There is no JVM, no Node.js, no .NET, no Python, no interpreter to warm up and no framework stack to configure. The Docker image is a multi-stage Alpine build containing nothing but the compiled OTP release — no Mix, no Hex, no source code, roughly 17 MB (compressed). It runs as a non-root user (UID 1001, GID 0) and is compatible with OpenShift’s arbitrary-UID injection.
Even the plumbing avoids weight: certificate generation uses Erlang’s built-in :public_key and :crypto, YAML parsing is pure Erlang with no C NIFs, and the whole service carries a handful of dependencies.
Stable and fast, because of the BEAM
Running on the Erlang virtual machine is not a stylistic choice. Every request is handled in its own lightweight process, so one slow or failing request cannot block or corrupt another; the scheduler is preemptive, so a /delay/30000 call does not stall the rest of the server; and an OTP supervision tree restarts what fails instead of taking the node down. This is the same runtime that has carried telecom switching for decades, and it is why a service this small can hold thousands of concurrent connections on modest hardware while idling at a few tens of megabytes.
Ready for production use as a test tool
1.0.0 is a hardening release as much as a feature release. XML element names are sanitised so header and query-parameter names cannot inject markup; chaos latency headers are range-validated so they cannot be turned into a denial of service; credential comparison for Basic auth and API keys runs over SHA-256 digests via :crypto.hash_equals/2 so neither value nor length leaks through timing; the bundled Swagger UI is version-pinned with Subresource Integrity; the generated server certificate carries a proper subjectAltName; and a half-configured TLS setup now aborts at startup instead of silently falling back. The test suite has grown to 180 tests, run in the container on every change.
Get it
One command, no configuration:
docker run -p 4000:4000 -p 4443:4443 svenwal/leywn:latest
Port 4000 serves plain HTTP, port 4443 serves HTTPS and mTLS with a self-signed certificate generated at startup. Open http://localhost:4000 and every endpoint is there, documented and executable in the browser.
- Docker Hub: https://hub.docker.com/r/svenwal/leywn
- Source: https://github.com/svenwal/leywn
- Live instance: https://leywn.org
- API docs: Swagger UI at
/, OpenAPI at/openapi.json, and a ready-made Insomnia collection at/request-collection - License: BSD 2-Clause