DEV Community

Cover image for Your First Deno Server in 60 Lines
Nick Scialli (he/him)
Nick Scialli (he/him)

Posted on • Originally published at typeofnan.dev

Your First Deno Server in 60 Lines

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
Enter fullscreen mode Exit fullscreen mode

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",
  },
];
Enter fullscreen mode Exit fullscreen mode

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 });
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 });
Enter fullscreen mode Exit fullscreen mode

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 });
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
purpledrgn profile image
purpledrgn

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.

Collapse
 
testosteronicv profile image
Vishal Yash Sharma

Amazing thank you. :)

Collapse
 
jonrandy profile image
Jon Randy ๐ŸŽ–๏ธ • Edited

I really do not see the point of TS. The 'types' in the above code buy you nothing whatsoever

Collapse
 
ionellupu profile image
Ionel Cristian Lupu

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

Collapse
 
nas5w profile image
Nick Scialli (he/him)

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.