The old implementation of StatusCodes was written in a hurry. To say the least, it was written in JavaScript (not TypeScript), had very clunky logic to read the URL, and opted to manipulate strings (within an array) in order to return a result. Feast your eyes on some deprecated code:
async function handleRequest(request) {
const requestURL = new URL(request.url);
// Isolate & sanitize the path
const requestPath = requestURL.pathname.substring(1);
const httpStatuses = [
"100 Continue",
"101 Switching Protocols",
"102 Processing",
"200 OK",
"201 Created",
"202 Accepted",
"203 Non-authoritative Information",
"204 No Content",
"205 Reset Content",
"206 Partial Content",
"207 Multi-Status",
"208 Already Reported",
"226 IM Used",
"300 Multiple Choices",
"301 Moved Permanently",
"302 Found",
"303 See Other",
"304 Not Modified",
"305 Use Proxy",
"307 Temporary Redirect",
"308 Permanent Redirect",
"400 Bad Request",
"401 Unauthorized",
"402 Payment Required",
"403 Forbidden",
"404 Not Found",
"405 Method Not Allowed",
"406 Not Acceptable",
"407 Proxy Authentication Required",
"408 Request Timeout",
"409 Conflict",
"410 Gone",
"411 Length Required",
"412 Precondition Failed",
"413 Payload Too Large",
"414 Request-URI Too Long",
"415 Unsupported Media Type",
"416 Requested Range Not Satisfiable",
"417 Expectation Failed",
"418 I'm a teapot",
"421 Misdirected Request",
"422 Unprocessable Entity",
"423 Locked",
"424 Failed Dependency",
"426 Upgrade Required",
"428 Precondition Required",
"429 Too Many Requests",
"431 Request Header Fields Too Large",
"444 Connection Closed Without Response",
"451 Unavailable For Legal Reasons",
"499 Client Closed Request",
"500 Internal Server Error",
"501 Not Implemented",
"502 Bad Gateway",
"503 Service Unavailable",
"504 Gateway Timeout",
"505 HTTP Version Not Supported",
"506 Variant Also Negotiates",
"507 Insufficient Storage",
"508 Loop Detected",
"510 Not Extended",
"511 Network Authentication Required",
"599 Network Connect Timeout Error"
]
// Filter array using the startsWith() method to find a matching HTTP Status Code
const foundStatus = httpStatuses.filter(httpStatus => httpStatus.startsWith(requestPath));
// Check if the pathname matches the validity of an HTTP Status Code
if (requestPath.length === 3 && foundStatus.length > 0) {
// Plaintext message for visual confirmation
return new Response(`HTTP ${foundStatus}`,{
// Return the actual three-digit matching HTTP Status Code
status: `${requestPath}`,
});
} else if (requestPath.length === 0) {
// Forward naked domain requests to info page
return Response.redirect(`https://statuscodes.org/about`, 301);
}
else {
// A plaintext error message, technically an Error 200 but here we are...
return new Response("Unsupported HTTP Status Code");
}
}
addEventListener("fetch", async event => {
event.respondWith(handleRequest(event.request))
})
Ouch! I decided to refactor it using Hono, a modern web framework built for the edge that has excellent TypeScript support, uses Web Standard APIs, and can deploy to Cloudflare Workers (among many other providers) out-of-the-box, lending itself to being an elegant and standardized choice.
Installing Hono
Intalling Hono is simple, a project can be bootstrapped with the following command:
npm create [email protected] my-app
You’ll be prompted for the runtime template, in this case I chose Cloudflare Workers:
? Which template do you want to use? » - Use arrow-keys. Return to submit.
aws-lambda
bun
cloudflare-pages
> cloudflare-workers
deno
fastly
lagon
nextjs
nodejs
The dependencies aren’t installed just yet, so running
npm i
in the project folder will fix that.
The src/index.ts
file will have some boilerplate code such as the Hono module import, an instance of Hono assigned to the app
variable, and a default return for the root path:
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Hello Hono!'))
export default app
The above is enough to get started, the command npm run dev
will fire up the Wrangler dev server on localhost:8787
Bootstrapping Data
The first thing to do was to place the list of HTTP status codes into a separate file, so I stored them in an array (of tuples) which ensured it was safely typed and easily importable into the index.ts
entrypoint:
// src/statusCodes.ts
export const statusCodes: Array<[number, string]> = [
[ 100, "Continue" ],
[ 101, "Switching Protocols" ],
[ 102, "Processing" ],
[ 200, "OK" ],
[ 201, "Created" ],
[ 202, "Accepted" ],
[ 203, "Non-authoritative Information" ],
[ 204, "No Content" ],
[ 205, "Reset Content" ],
[ 206, "Partial Content" ],
[ 207, "Multi-Status" ],
[ 208, "Already Reported" ],
[ 226, "IM Used" ],
[ 300, "Multiple Choices" ],
[ 301, "Moved Permanently" ],
[ 302, "Found" ],
[ 303, "See Other" ],
[ 304, "Not Modified" ],
[ 305, "Use Proxy" ],
[ 307, "Temporary Redirect" ],
[ 308, "Permanent Redirect" ],
[ 400, "Bad Request" ],
[ 401, "Unauthorized" ],
[ 402, "Payment Required" ],
[ 403, "Forbidden" ],
[ 404, "Not Found" ],
[ 405, "Method Not Allowed" ],
[ 406, "Not Acceptable" ],
[ 407, "Proxy Authentication Required" ],
[ 408, "Request Timeout" ],
[ 409, "Conflict" ],
[ 410, "Gone" ],
[ 411, "Length Required" ],
[ 412, "Precondition Failed" ],
[ 413, "Payload Too Large" ],
[ 414, "Request-URI Too Long" ],
[ 415, "Unsupported Media Type" ],
[ 416, "Requested Range Not Satisfiable" ],
[ 417, "Expectation Failed" ],
[ 418, "I'm a teapot" ],
[ 421, "Misdirected Request" ],
[ 422, "Unprocessable Entity" ],
[ 423, "Locked" ],
[ 424, "Failed Dependency" ],
[ 426, "Upgrade Required" ],
[ 428, "Precondition Required" ],
[ 429, "Too Many Requests" ],
[ 431, "Request Header Fields Too Large" ],
[ 444, "Connection Closed Without Response" ],
[ 451, "Unavailable For Legal Reasons" ],
[ 499, "Client Closed Request" ],
[ 500, "Internal Server Error" ],
[ 501, "Not Implemented" ],
[ 502, "Bad Gateway" ],
[ 503, "Service Unavailable" ],
[ 504, "Gateway Timeout" ],
[ 505, "HTTP Version Not Supported" ],
[ 506, "Variant Also Negotiates" ],
[ 507, "Insufficient Storage" ],
[ 508, "Loop Detected" ],
[ 510, "Not Extended" ],
[ 511, "Network Authentication Required" ],
[ 599, "Network Connect Timeout Error" ]
]
It was then imported into the index.ts
file:
import { statusCodes } from './statusCodes'
Adding Logic
The key to this entire project is the actual status code, so being able to lookup the code quickly and accurately is important. An array isn’t an ideal data structure to achieve this, therefore using a Map is better since the keys can be numbers, and lookups are very performant:
const allCodes: Map<number, string> = new Map(statusCodes)
TypeScript will infer the type from the Map
keyword, but I like explicitly setting the type as a Map of tuples just to be thorough.
Hono offers an extensive but simple API when responding to GET
requests from the Context
object. The structure should be idiomatic and easy to understand, but you can read more about it in the docs: hono.dev/api/context#set-get
app.get('/:code', (c) => {
/*
...
*/
})
Hono ingests the code
URL parameter as a string, but I needed to parse this as an integer to be able to lookup the Map keys (which themselves are numbers):
let statusCode: number | any = parseInt(c.req.param('code'))
I used a
number | any
type union because TypeScript shows an error when returning just anumber
type in theResponse
object due to overloads. There’s most likely a proper a fix for this but laziness took over so here we are.
The actual logic then involves looking up the (parsed) URL parameter to see if a corresponding Map key exists, returning the value of that key as the actual HTTP response code, and also interpolating both the key and value as a text/plain
response to be as helpful as possible:
if (allCodes.has(statusCode)) {
c.header('Content-Type', 'text/plain')
return c.text(`${statusCode} ${allCodes.get(statusCode)}`, statusCode)
}
return c.redirect('https://about.statuscodes.org', 302)
c.header
is simply Hono’s method for adding headers to theContext
object, which handles Request and Response. While I addedstatusCode
to thereturn
statement for brevity, it could also have been set viac.status(statusCode)
If the Map key doesn’t exist, then the URL parameter isn’t a valid status code, so regardless of the query the result should be a 302
redirect to the project’s about page:
return c.redirect('https://about.statuscodes.org', 302)
Similarly, any requests to the naked domain should result in a 301
permanent redirect to the project’s about page:
app.get('/', (c) => c.redirect('https://about.statuscodes.org', 301))
In both cases, Hono’s simplicity allows for creating an inline redirect using the
c.redirect()
Context method (with the302
or301
redirect codes passed as parameters).
Deploying Solution
The final Cloudflare Worker code looked like this:
// src/index.ts
import { Hono } from 'hono'
import { statusCodes } from './statusCodes'
const app = new Hono({ strict: false })
app.get('/:code', (c) => {
const allCodes: Map<number, string> = new Map(statusCodes)
// the URL param is a string, but the Map key is a number
let statusCode: number | any = parseInt(c.req.param('code'))
if (allCodes.has(statusCode)) {
c.header('Content-Type', 'text/plain')
return c.text(`${statusCode} ${allCodes.get(statusCode)}`, statusCode)
}
// 302 redirect since incorrect paths will be arbitrary
return c.redirect('https://about.statuscodes.org', 302)
})
// 301 redirect for SEO when hitting the naked domain
app.get('/', (c) => c.redirect('https://about.statuscodes.org', 301))
export default app
Deploying was as simple as running npm run deploy
and you can find the live project over at statuscodes.org
As I mentioned before, Hono is extremely performant and even more so when paired with Cloudflare’s edge network. Response times are generally below 2ms
worldwide: check-host.net/check-ping?host=https://statuscodes.org/200
Links
- StatusCodes (GitHub) - github.com/paramdeo/statuscodes.org
- Hono - hono.dev