Deno by example: Content Server — Part 1 Introduction
Welcome to the medium course:
Learn Deno by example — Content server.
This is a medium course that takes the user from writing, testing, formatting, logging, documenting, and deploying an application in Deno. This rapid course consists of six sections.
Here are the course contents:
- Section 1: Introduction to the content server (this article)
- Section 2: Formatting & linting
- Section 3: Unit testing
- Section 4: Integration & performance testing
- Section 5: Logging, tracking & documenting
- Section 6: Deployment on Deno Deploy
This is the first section of the course. Let’s get started!
Introduction to Deno
Deno is a simple, modern, and secure runtime for JavaScript and TypeScript that uses V8 and is built in Rust programming language. Deno was generally available in May 2020 and has seen 16 major releases since then.
Deno has a number of unique features like:
- Secure by default: No file, network, or environment access, unless explicitly enabled
- Supports TypeScript out of the box
- Ships only a single executable file
- Contains a complete development tool chain
- Has built-in utilities like a dependency formatter, linter, bundler, tester, etc.
- Has a set of reviewed (audited) standard modules that are guaranteed to work with Deno: deno.land/std
Like Node.js, Deno’s async runtime is ideally suited for web apps that performs mostly network and disk ops, and is relatively light on CPU intensive ops. One of the heavily used web app is the content server. A static content server (part of the CDN) is commonly used to serve static files like HTML, CSS, JavaScript, images, video, etc. Serving content fully suits the async model of runtimes like Deno. This is because serving content isn’t a CPU intensive operation. It’s mostly I/O bound.
In this course, we’ll write a simple static content server and take it through the entire cycle of formatting, linting, unit testing, integration testing, performance testing, logging, documenting, and deploying.
Content server
A static content server is a fairly simple application. All it does is to get the file path from the request and sends the file back. Some content servers need authorization to access files, while others serve openly (public CDNs don’t use authorization).
Our content server application has five parts:
Main module (main.ts)
The main module is like the usual main program. This is the starting point. Our main module does the following:
- Check if serve path is readable
- Check if api keys path is readable
- Check if log file path is writable
- Start an HTTP server and hand over all incoming requests to router
Router (router.ts)
In this particular case, the router is very simple. All it does is following:
- Reject with 405 if the request method is not GET
- Hand over request to controller
- Any exceptions from controller are sent as 500 Internal server error
Controller (controller.ts)
The controller is the place where the request is handled, and a response is sent to the caller. The HTTP handling (request, response) doesn’t travel beyond this point. The controller does the following:
- Authorize the request using authorization service module (authService)
- Get the file length & file stream using file service module (fileService)
- Convert exceptions from the above services to HTTP status codes
- Prepares response object with file or proper status codes
File service (fileService.ts)
This is the service that returns the file size & the file stream. The file service does the following:
- Get full path using base path (either provided through command line or default path from cfg.json) and relative path
- Get the length of the file (using Deno.stat)
- Open the file path and get a Deno.Reader
- Convert Deno.Reader to ReadableStream using a utility from the standard library (readableStreamFromReader)
Auth service (authService.ts)
The auth service optionally imposes authorization through API keys. For simplicity, the API keys are loaded from a local JSON file. The auth service does the following:
- Load API keys JSON (the path is present in cfg.json)
- Consider authorized if API keys are empty
- Parse Authorization header and check if API key is valid
Constants (consts.ts)
This is the place where the global constants are kept. It also contains a simple KV pair to map file extensions to content type.
Code
The following is the initial code of the content server. It’s called initial code because we’ll be improving on this through formatting, linting, logging, and documenting.
The code is fairly short (131 lines in total). Here is the breakup:
21 authService.ts
4 cfg.json
34 consts.ts
27 controller.ts
14 fileService.ts
23 main.ts
8 router.ts
131 total
The final content server application is also hosted in a public GitHub repo. This is the final application that’s the output of this complete course. We’ll be enhancing this application throughout the course.
Manual tests
The first way to test the above application is by running manual tests while the application is being developed. As the application is HTTP based, some common ways are: Curl, Postman, Insomnia, Testfully, etc. We’ll use curl for initial testing.
First, let’s see how to run the service. Then we’ll run some manual tests.
Running
There are two ways to run the content server service:
- Clone the repo and run
- Run it from URL
Clone & run
The repo can be cloned the usual way. Once it’s available locally, the service can be started as follows:
$ deno run --allow-net=:8080 --allow-read=/var/tmp/testdata/,./ main.ts /var/tmp/testdataContent server started...//or, use default path from cfg.json$ deno run --allow-net=:8080 --allow-read=./ main.tsContent server started...
Run from URL
The other way is to run directly from the GitHub URL. This is one of the great advantages of Deno. Deno will download the main module (main.ts) and all the dependencies.
$ deno run --allow-net=:8080 --allow-read=/var/tmp/testdata/,./ https://raw.githubusercontent.com/mayankchoubey/deno-content-server/main/main.ts /var/tmp/testdataContent server started...
Tests
Here are some curl tests once the initial cut is ready. We’ll run both positive and negative tests.
Negative tests
The negative tests are those that doesn’t get a file to the caller.
$ curl http://localhost:8080
< HTTP/1.1 422 Unprocessable Entity
< content-length: 0$ curl http://localhost:8080 -X POST
< HTTP/1.1 405 Method Not Allowed
< content-length: 0$ curl http://localhost:8080 -X DELETE
< HTTP/1.1 405 Method Not Allowed
< content-length: 0$ curl http://localhost:8080/abcd/sample.txt
< HTTP/1.1 404 Not Found
< content-length: 0$ curl http://localhost:8080/someDir
< HTTP/1.1 422 Unprocessable Entity
< content-length: 0
Positive tests
The positive tests are those that does get a file to the caller.
$ curl http://localhost:8080/sample.txt
< HTTP/1.1 200 OK
< content-length: 22
< content-type: text/plain
Learning Deno Is Fun!$ curl http://localhost:8080/someDir/someDir2/someDir3/someDir4/someDir5/someFile.txt
< HTTP/1.1 200 OK
< content-length: 22
< content-type: text/plain
Learning Deno Is Fun!$ curl http://localhost:8080/sample.html
< HTTP/1.1 200 OK
< content-length: 44
< content-type: text/html
<HTML>
<BODY>
<h1>Hello World!!</h1>
</BODY>
</HTML>$ curl http://localhost:8080/sample.js -o /dev/null
< HTTP/1.1 200 OK
< content-length: 288580
< content-type: text/javascript
/* OUTPUT REDIRECTED */$ curl http://localhost:8080/sample.pdf -o /dev/null
< HTTP/1.1 200 OK
< content-length: 69273
< content-type: application/pdf
/* OUTPUT REDIRECTED */
All good! The simple content server works fine. We’re done with the first section of the course.
Here is the map of this rapid medium course:
- Section 1: Introduction to content server (this article)
- Section 2: Formatting & linting
- Section 3: Unit testing
- Section 4: Integration & performance testing
- Section 5: Logging, tracking & documenting
- Section 6: Deployment on Deno Deploy
In the next section, we’ll go over formatting and linting.
This story is a part of the exclusive medium publication on Deno: Deno World.