Your First Deno Server in 60 Lines
Nick Scialli (he/him)
Posted on May 18, 2020
Today we're going to write our first Deno server in 60 lines. Deno is self-described as a simple, modern and secure runtime for JavaScript and TypeScript that uses V8 and is built in Rust." I'm a huge fan of typescript, so I was really excited to hear about a runtime that treated typescript as a first-class citizen!
Learn More
If you like this post, consider checking out my free mailing list and YouTube tutorials to learn more JavaScript- and Typescript-related things!
Getting Started
First, we have to install the runtime. There are a lot of OS-dependent ways to do this, so I'm going to refer you to the Deno docs to get it installed.
Once Deno is installed, you should be able to type deno --version
into your command line and see something like this:
deno 1.0.0
v8 8.4.300
typescript 3.9.2
Deno is young and moving fast, so I wouldn't be surprised if you have a newer version!
Specifying our Domain
For our first server, let's pretend we're maintaining some sort of virtual bookshelf. Our domain, therefore, deals with books. We can use typescript to specify our Book
type and create an array of books
with an initial item. Let's create this file in a new directory and call the file server.ts
:
server.ts
type Book = {
id: number;
title: string;
author: string;
};
const books: Book[] = [
{
id: 1,
title: "The Hobbit",
author: "J. R. R. Tolkien",
},
];
Grabbing a Server Library
The oak server library appears to be, thus far, the most ubiquitous server library for deno. Let's use it!
If you're familiar with node, you might think we use an install command and maintain our version in some kind of package.json-like file. Not so! Instead, we specify the package url in our import statement and pin down the version within the import. Deno will first see if we have a cached version of the resource and, if not, will fetch and cache it.
Importantly, note that we specify version 4.0.0 of oak. If we don't specify the version, we'll just get the latest! Seems dangerous given the possibility of breaking changes along the way.
We're going to import Application
and Router
from oak. These will create our app server and allow us to configure routes, respectively.
We can add a get
route to our root url to respond with "Hello world!" We tell our app to listen on port 8000.
import { Application, Router } from "https://deno.land/x/oak@v4.0.0/mod.ts";
const app = new Application();
const router = new Router();
router
.get("/", (context) => {
context.response.body = "Hello world!";
})
app.use(router.routes());
await app.listen({ port: 8000 });
This is a functioning server, so we should test it out! In the directory with your file, run the following command:
deno run --allow-net server.ts
Your app is now listening on port 8000, so you should be able to navigate to http://localhost:8000
in your browser and see our Hello World example!
Add Our Routes
We can now add some routes! I will set up some common CRUD routes on our book resource: get book
to see all books, get book:id
to see a specific book, and post book
to create a book.
import { Application, Router } from "https://deno.land/x/oak@v4.0.0/mod.ts";
type Book = {
id: number;
title: string;
author: string;
};
const books: Book[] = [
{
id: 1,
title: "The Hobbit",
author: "J. R. R. Tolkien",
},
];
const app = new Application();
const router = new Router();
router
.get("/", (context) => {
context.response.body = "Hello world!";
})
.get("/book", (context) => {
context.response.body = books;
})
.get("/book/:id", (context) => {
if (context.params && context.params.id) {
const id = context.params.id;
context.response.body = books.find((book) => book.id === parseInt(id));
}
})
.post("/book", async (context) => {
const body = await context.request.body();
if (!body.value.title || !body.value.author) {
context.response.status = 400;
return;
}
const newBook: Book = {
id: 2,
title: body.value.title,
author: body.value.author,
};
books.push(newBook);
context.response.status = 201;
});
app.use(router.routes());
app.use(router.allowedMethods());
await app.listen({ port: 8000 });
I think the only bit of this code that might be new or unexplained is app.use(router.allowedMethods());
. This is simply a handy middleware that will let clients know when a route method is not allowed!
Final Touch: Logging Middleware
Let's add one final touch: logging middleware that logs how long each request takes:
import { Application, Router } from "https://deno.land/x/oak@v4.0.0/mod.ts";
type Book = {
id: number;
title: string;
author: string;
};
const books: Book[] = [
{
id: 1,
title: "The Hobbit",
author: "J. R. R. Tolkien",
},
];
const app = new Application();
// Logger
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
console.log(`${ctx.request.method} ${ctx.request.url} - ${ms}ms`);
});
const router = new Router();
router
.get("/", (context) => {
context.response.body = "Hello world!";
})
.get("/book", (context) => {
context.response.body = books;
})
.get("/book/:id", (context) => {
if (context.params && context.params.id) {
let id = context.params.id;
context.response.body = books.find((book) => book.id === parseInt(id));
}
})
.post("/book", async (context) => {
const body = await context.request.body();
if (!body.value.title || !body.value.author) {
context.response.status = 400;
return;
}
const newBook: Book = {
id: 2,
title: body.value.title,
author: body.value.author,
};
books.push(newBook);
context.response.status = 201;
});
app.use(router.routes());
app.use(router.allowedMethods());
await app.listen({ port: 8000 });
Now whenever you hit our server, the route path and the amount of time it takes to send a response will be logged to the console.
Fin
And there you have it! Our first Deno server in 60 lines. I'm a huge fan of Deno and looking forward to learning more about it as it evolves. I do have some questions and concerns (for example, given the lack of a lockfile, I'm wondering if and how Deno will allow developers control over indirect dependencies), but for now I'm just enjoying tinkering with this new toy.
Learn More
If you like this post, consider checking out my free mailing list and YouTube tutorials to learn more JavaScript- and Typescript-related things!
Posted on May 18, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.