Server-Side Development

First Web Applications with Deno


Learning Objectives

  • You know what Deno is and can build a web application with Deno.
  • You know of different ways of using information from HTTP requests, including the path, parameters, and the method.

Web developers work with libraries and frameworks that abstract away the need to explicitly parse HTTP requests and responses, making it easier to focus on what the application should do.

In this course, when building server-side functionality, we use Deno, a runtime for running JavaScript and TypeScript code.

Although the examples here have been written so that they work without the walking skeleton, you may use the walking skeleton for the examples. In that case, work on the codes so that you export them from app.js to app-run.js, which is used as the entry point for starting the server.

A web application with Deno

Deno is a runtime for running code. You can install it by following Deno installation guidelines.

Creating a simple web application with Deno is straightforward. The following code demonstrates a one-liner that could be used to create a server that responds to requests with the string “Hello world!”.

Deno.serve((request) => new Response("Hello world!"));

Once you have installed Deno, you can run code using Deno’s run command from the command line. In your local file system, create a file called app.js and copy the above code to the file. Then, in the same folder in the terminal, run the following command.

deno run --allow-net --watch app.js

This runs the code, starting a server that responds to requests. When the server is running, open up the address http://localhost:8000 in the browser on your local computer. The address translates to the port 8000 on your local computer.

When you open up the address, the browser should show you the text “Hello world!”. If it does not, check that the server is running, that the address is correct, and that you do not have e.g. a firewall blocking the connection.

The Deno.serve command is given a function that takes an instance of a Request as a parameter and returns a Response. The one-liner is functionally equivalent to the following code, where we explicitly define a function and give it to the Deno.serve command.

const handleRequest = (request) => {
  return new Response("Hello world!");
};

Deno.serve(handleRequest);
Starting and stopping the server

The command deno run --allow-net --watch app.js runs the code in the file app.js and starts the server. The flag --allow-net allows Deno to access the network, which is necessary for running a server. The flag --watch tells Deno to watch for changes in the file app.js and restart the server when the file changes.

To stop the server, press Ctrl + C on the keyboard (or, some other combination indicating cancel, depending on your operating system).

If you do not stop the server and try to run the command again, you will get an error message stating that the port is already in use.

The above could be further divided into two files, app.js and app-run.js. The app.js file contains the application code, and the app-run.js file is used to run the application. In this case, the file app.js would be as follows.

const handleRequest = (request) => {
  return new Response("Hello world!");
};

export default handleRequest;

And the app-run.js file would be as follows.

import handleRequest from "./app.js";

Deno.serve(handleRequest);

This is the format that is used in many of the exercises. Only modify the functionality in app.js, and when testing locally, run the server using the app-run.js file.

Loading Exercise...

Accessing the server from the command line

You can also try querying the application from the command line using the curl tool. When you have curl installed, open a separate terminal window and run the following command.

curl http://localhost:8000

The command opens a connection to the server running on your local computer and requests the resource at the root path of the server. Then, the command prints the response from the server to the terminal window.

As we might guess, when the server is running, the response is as follows.

Hello world!

Modify the server code to match the following and run the server again. Now, the server logs “Hello server logs!” to the terminal window where the server is running every time a request is made.

const handleRequest = (request) => {
  console.log("Hello server logs!");
  return new Response("Hello world!");
};

Deno.serve(handleRequest);

Note that the output is in the terminal window where the server is running, not in the terminal window where you made the request using curl.

Optional JavaScript primer

The course expects that you have prior programming experience. Prior JavaScript knowledge is not required. At this point, if you haven’t worked with JavaScript, check out the optional JavaScript primer in course practicalities.

Information from requests

Requests in Deno follow the Web Fetch API Request interface specification. We can access request information through the instance properties of Request. As an example, to log the request method and the url, we can use the method and url properties of the request.

const handleRequest = (request) => {
  console.log(`Request method: ${request.method}`);
  console.log(`Request url: ${request.url}`);
  return new Response("Hello world!");
};

Deno.serve(handleRequest);

The requests can contain a variety of information, such as the request method, the requested URL, and request parameters.

Keep in mind that the requests and responses follow the HTTP protocol. Deno and web application libraries and frameworks in general provide an abstraction for working with HTTP requests and responses.

The URL-class provides convenience methods for working with URLs, allowing easy access to the requested path, parameters, and other parts of the URL. An instance of the URL-class can be created from the url property of the request.

const handleRequest = (request) => {
  const url = new URL(request.url);
  // let's do something with the url

  return new Response("Hello world!");
};

Deno.serve(handleRequest);

Requested paths

The path of the URL, e.g. “/hello” from “http://localhost:8000/hello”, is stored in the property pathname of the URL-object.

The following example outlines a server that responds with the message “world” to requests to path “/hello” and with the message “ingredient” to any path containing the string “secret”. Other requests receive the message “hello” as the response.

const handleRequest = (request) => {
  const url = new URL(request.url);

  let message = "hello";
  if (url.pathname === "/hello") {
    message = "world";
  } else if (url.pathname.includes("secret")) {
    message = "ingredient";
  }

  return new Response(message);
};

Deno.serve(handleRequest);
Loading Exercise...

Request parameters

Request parameters are included at the end of the path after a question mark. As an example, the URL http://localhost:8000?name=Harry includes a parameter “name” with the value “Harry”.

Request parameters are stored in the searchParams property of the URL-object. The searchParams-property is an instance of the URLSearchParams class.

The following example outlines a server that responds with the value of the parameter name in the request.

const handleRequest = (request) => {
  const url = new URL(request.url);
  const params = url.searchParams;
  return new Response(`Name: ${params.get("name")}`);
};

Deno.serve(handleRequest);

When we try out the server, we see that the name is shown if it exists in the request parameters.

curl "http://localhost:8000?name=Harry"
Name: Harry%
curl "http://localhost:8000"
Name: null%

Parameters are separated by an ampersand ”&”. As an example, the URL http://localhost:8000?name=Harry&age=42 includes two parameters, “name” with the value “Harry” and “age” with the value “42”.

As HTTP is a text-based protocol, request parameters and their values are always strings.

If we wish to process request parameter values as numbers, we need to explicitly convert them to numbers. This can be done using the Number class. The following application outlines how a simple calculator would be implemented. The application expects two parameters, “one” and “two”, and returns the sum of their values.

const handleRequest = (request) => {
  const url = new URL(request.url);
  const params = url.searchParams;

  const one = Number(params.get("one"));
  const two = Number(params.get("two"));

  return new Response(`Sum: ${one + two}`);
};

Deno.serve(handleRequest);
curl "http://localhost:8000?one=1&two=2"
Sum: 3%
curl "http://localhost:8000?two=123"
Sum: 123%
curl "http://localhost:8000?one=1&two=123"
Sum: 124%
Template literals

In the above example, we used template literals. Template literals provide an easy way to construct strings with embedded expressions that are evaluated when the strings are constructed.

const one = 1;
const two = 2;
// logs "Sum: 3"
console.log(`Sum: ${one + two}`);
 // logs "Sum: 1 + 2 = 3"
console.log(`Sum: ${one} + ${two} = ${one + two}`);

Loading Exercise...

Request methods

Each HTTP request has a request method, such as GET or POST. The request method can be accessed through the property method of the request. The following application would respond with the request method.

const handleRequest = (request) => {
  return new Response(request.method);
};

Deno.serve(handleRequest);

By default, curl makes a GET request to the server.

curl http://localhost:8000
GET%

In order to make another type of a request with curl, we need to use flag -X to pass the request method as a parameter. In the following example, we first make a request using the request method POST, after which we try to make a request with a request method called Alohomora.

curl -X POST http://localhost:8000
POST%
curl -X Alohomora http://localhost:8000
Alohomora%

Even though Alohomora is not a real request method, it still works. In practice, it is both the browser’s and the server’s responsibility to make sure to adhere to existing standards and protocols. One can, on the other hand, also invent new ones.

An an example, the HTTP status code 418 defined in RFC 2324 is “I’m a teapot”. The RFC (and the status code) was originally an April Fools joke, but the status code is still around.

Loading Exercise...

In the following example, the server responds with the message “Retrieving information, are you?” to GET requests, “Posting information, are you?” to POST requests, and with the message “Magicking, are you?” to requests made with Alohomora (that is not part of the HTTP specification). If the request method is something else, the server responds with the message “I am unsure what I should do here!”.

const handleRequest = (request) => {
  let message = "I am unsure what I should do here!";
  if (request.method === "GET") {
    message = "Retrieving information, are you?";
  } else if (request.method === "POST") {
    message = "Posting information, are you?";
  } else if (request.method === "Alohomora") {
    message = "Magicking, are you?";
  }

  return new Response(message);
};

Deno.serve(handleRequest);
Loading Exercise...