A Native Deno Webserver

John Coonrod
Tech Tonic
Published in
3 min readJul 25, 2021

--

How to use the built in functions

As my dozen or so readers know, I really hate using more third-party files than is absolutely necessary. That has attracted me to Deno, the cool new replacement for Node.js as it really eliminates the need for package managers. I’m using it to try and clone WordPress in JavaScript in a toy project that I’m calling DenoPress. I started by using the lovely Drash add-on but realized that it was really not necessary.

This public github repository is a very simple example of how to create a Deno webserver using only the native Deno functionality to serve both static and dynamic components without needing to import anything. You can run this as is and get a Hello World example that includes a style.css file and an image file. To walk you through the code, at the very bottom of server.ts you see the main code which pulls in a map of mimetypes and a template, instantiates a listener, and then loops to handle requests:

const page404='<html><body><h1>Object not found</h1></body></html>';const mimetypes: Record<string,string>={'svg':'image/svg+xml','jpg':'image/jpeg','png':'image/png','txt':'text/plain','css':'text/css','jpeg':'image/jpeg'};const hello=await Deno.readFile('./template.html');
const server = Deno.listen({ port: 8080 });
for await (const conn of server) {handle(conn);}

The first line is a simple response body for ‘file not found’ errors.

As I’m a TypeScript newby, it took me a few tries to realize that to make an associative map of mimetypes to file extensions I’d need to make sure the system knows the “Record<string,string>” bit about the map object.

Then, all we need to understand is the request handler “handle” at the top of the file:

async function handle(conn: Deno.Conn) {
const httpConn = Deno.serveHttp(conn);
for await (const requestEvent of httpConn) {
const url = new URL(requestEvent.request.url);
const p=url.pathname;
const mime=String(mimetypes[p.substr(p.lastIndexOf('.')+1)]);
if(p=='/favicon.ico' || p=='/robots.txt'||p.substr(0,7)=='/static') {
try {
await Deno.stat("."+p);
const buf=await Deno.readFile("."+p);
await requestEvent.respondWith(
new Response(buf,{status:200, headers:{"Content-type":mime}}));
} catch(_error) {
await requestEvent.respondWith(
new Response(page404, {status: 404, headers:{"Content-type":"text/html"}}));}
}else{
await requestEvent.respondWith(
new Response(hello, {status: 200, headers:{"Content-type":"text/html"}}));
}
}
}

This function establishes an Http server connection and then waits for a request. When it gets one, it creates a URL object, gets its pathname, and the mimetype that corresponds to the file requested.

Next it decides whether to simply serve a static file or do something more dynamic — which in my case is to simply spit out the template stored in the hello string and include the path that was in the url.

If it serves a static file, and if that file exists, it simply pulls it into a buffer using the Deno.readfile function — a wonderfully powerful feature that doesn’t seem to appear in other people’s examples.

The “try/catch” combo awaiting Deno.stat() handles a proper 404 response if the file is not found. Try asking for /static/nothere.jpg and you’ll see.

To keep things secure, it will only serve static files that are either in the static directory or favicon.ico or robots.txt (which want to be in the root area).

This is the example I wish I’d found early on, and I hope it is useful. I added enough to the template to ensure I could get a perfect Lighthouse score and a mobile-ready responsive layout.

--

--