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!
Top comments (6)
Whats the point of having dependencies tied to each file? If I have a dependency to a state system or react doesn't that mean I have to go though refactoring hell to change every single instance in the entire application to use the one bugfix version (hard to find, screw it up once and you have to comb 1000 files and code to find the one thats different, etc). I have seen many instances where I've had to rollback to a very specific version due to a particular problem with just a bugfix release. Even the recent NPM7 talk shows how you may need to rollback a bugfix release to "fix" a security flaw with a dependency--and thats just the tip of the iceberg with dependency hell.
And speaking of security. This --allow-net feature has little to no meaning. It's clear from the code that it's just "do anything you want" only you have to the extra burden on the user to specify it (and presumably know to specify it if you have something that only sometimes needs network). This smells like a "we gave you thing, if it doesnt work it's your fault #covermyass" kind of feature. In the code there's nothing that says "only this library can have access to network" so any random dependency or dependency of a dependency can have malicious code in it. It's only small merit for 10 line scripts but I'm sure people will just suggest running --allow-net on anything and everything out of sheer frustration with cases where it is legitimately needed and not obvious you had to put it there. For any serious application you will need net and fs for something or other (database and logging just to name the most obvious) so it's just a flag that's always there that gives people who don't know how to make a system secure a false sense of security and safety thats not actually there. If this is what the Deno team considers security to be I'm very skeptical about the future of the language.
All in all, this is just typescript which you can already write (ts-node also exist that simplify the process of just running typescript if you really want a one-liner). Inferior ecosystem and more importantly security in said ecosystem (since at least NPM is making serious efforts--not that malicious packages are unique to js or anything).
This project should have stayed in the oven. This 1.0.0 doesn't seem to have any redeeming qualities. Besides some potential in splitting the community apart, what exactly does it add for the javascript/typescript community.
Amazing thank you. :)
I really do not see the point of TS. The 'types' in the above code buy you nothing whatsoever
Using TS is an investment. It will payoff in the long run. It will actually make you write less code. An a more solid code
I get that critique for smaller apps. It doesn't buy you a whole lot for this 60-liner. But for the large React app with a complex state tree I work on in my day job, Typescript has been incredible. Having the Typescript compiler say "nope" immediately when you've done something wrong is incredibly helpful, and the associated linting means I usually catch it as I'm typing.
Some comments may only be visible to logged-in visitors. Sign in to view all comments.