Serverless APIs with Deno and Vercel

Vercel has created a zero config serverless platform, by adding files in an api folder you create api endpoints, no need for a server framework or listen to port command, you just need to bring your functions.  🚀

Deno is the new typescript/javascript runtime from the NodeJS creator Ryan Dahl. The runtime has new features that create a more secure environment. Deno has several coding tools built in, like a test runner, linter and even a typescript compiler. One of the most controversial attributes it the ability to pull code from a url. Deno removes itself from the need to use a second client to install third party code, instead Deno leverages the the url system of the internet. 🦕

The team at Vercel has created a runtime for Deno. Let's create a serverless api with Deno and launch it on Vercel.

If you are just kicking the tires and don't want to install vercel and deno locally, you can use Gitpod. Gitpod is an online code IDE that gives you a sandbox per code repository. Simply, fork this repository https://github.com/hyper63/gitpod-deno-template and launch it in Gitpod with the Gitpod extension https://chrome.google.com/webstore/detail/gitpod-dev-environments-i/dodmmooeoklaejobgleioelladacbeki?hl=en

Installing Deno

Deno ships as a single executable with no dependencies, this makes the install process  straight forward: see https://deno.land for more information about your platform. If you are on a Mac you can run:

Shell (Mac, Linux)

curl -fsSL https://deno.land/x/install/install.sh | sh

Windows Powershell

iwr https://deno.land/x/install/install.ps1 -useb | iex

Need more options: https://github.com/denoland/deno_install

Setting up Vercel

Vercel is a platform as service that focuses on static generated web sites and serverless api functions. Built to host JAM stack applications, Javascript, APIS, and Markup.

To signup you will need an account from either Github, Gitlab, or Bitbucket, these services are code repositories and Vercel is able to create continuous delivery pipelines from any of these services. Once you have your code repository account, go to the Vercel signup page and login with your account. https://vercel.com/signup follow the steps to create your free personal account.

To use Vercel locally, you will need NodeJS and NPM installed, follow the instructions here to install the CLI - https://vercel.com/download

Getting started

So, now that we have all of our setup complete, it is now time to decide what kind of API are we going to build? Hmm! Let's build a COWSAY API, COWSAY is a program that takes some text as input and returns an ASCII rendering of a cow with a bubble enclosing the text.

_______
< hello >
 -------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

In our API, we will create two endpoints:

  • GET / - returns a welcome message with a cow
  • POST /say - allows the client to send a JSON body with some text and type of animal.

Sound like fun?

Setup project

Ok, let's do this!

Open a local terminal or console

Create a new folder on your local machine called cowsay

mkdir cowsay
cd cowsay

In this directory, we want to create a vercel.json file. This file will allow us to specify the Deno runtime for our functions.

{
  "functions": {
    "api/**/*.[jt]s": { "runtime": "vercel-deno@0.7.7" }
  }
}
At the time of this writing, the current version of the vercel-deno runtime was 0.7.7. You will want to check the repository to confirm the latest version: https://github.com/TooTallNate/vercel-deno

Create GET / endpoint

Now that we have the configuration out of the way, we can create our first endpoint.

Create a new folder called api and in that folder create a new file called index.ts.

mkdir api
touch api/index.ts

Open index.ts in your code editor

import { ServerRequest } from 'https://deno.land/std@0.89.0/http/server.ts'

export default (req: ServerRequest) => req.respond({
  body: 'Hello World'
})

We will import the ServerRequest type from the http/server module in Deno. Then we will export a default function that received the ServerRequest object from Vercel when the / path is requested. Vercel maps any index file to the root / path of the api. So by naming the file index we specify that we want this function to be invoked when the client requests the root / path. In Deno, there is no second object passed in. The ServerRequest object has a method called respond this method takes a Response Object. This object should contain a body property, but also can contain a status and a headers property. You can see the interface declaration here:

/**
 * Interface of HTTP server response.
 * If body is a Reader, response would be chunked.
 * If body is a string, it would be UTF-8 encoded by default.
 */
export interface Response {
  status?: number;
  headers?: Headers;
  body?: Uint8Array | Deno.Reader | string;
  trailers?: () => Promise<Headers> | Headers;
}

https://deno.land/std@0.89.0/http/server.ts#L388

Now, that we defined our new endpoint, save the file and lets start the local Vercel dev server. First lets make sure the Vercel CLI is installed:

npm i -g vercel

Next, login to your account using the CLI

vercel login

Enter your email address and then go to your email client and click verify, this will connect your client to your Vercel account.

Finally, we can launch the dev environment by typing:

vercel dev

When you press enter, you will be prompted with a couple of questions:

Vercel CLI 21.3.3 dev (beta) — https://vercel.com/feedback
? Set up and develop “/workspace/cowsay”? [Y/n] y
? Which scope should contain your project? tom-personal
? Link to existing project? [y/N] n
? What’s your project’s name? cowsay
? In which directory is your code located? ./
No framework detected. Default Project Settings:
- Build Command: `npm run vercel-build` or `npm run build`

You should be able to accept all of the default settings, by just clicking enter.

Finally, you should see the following:

Vercel CLI 21.3.3 dev (beta) — https://vercel.com/feedback
> Ready! Available at http://localhost:3000

✨ Yay! ✨ you should be running a serverless environment locally with Deno.

Let's confirm, open up a browser to this url https://localhost:3000/api  and you should see 'Hello World'.

What does the COWSAY?

Now that we have our server working, let's create a new endpoint to accept a JSON document and return a cow.

We will be using this curl command to test our new endpoint.

curl -X POST localhost:3000/api/say -d '{"text": "Cows Rock!" }' -H 'Content-Type: application/json'

Create a new file in the api folder called say.ts

import { ServerRequest } from '../deps.ts'

export default (req: ServerRequest) => {

}

And lets create a dependency file so that we don't have to type urls all over the place. Create this file in your project root directory and call it deps.ts

export { ServerRequest } from 'https://deno.land/std@0.89.0/http/server.ts'

Parsing the body of a request

The first thing we need to with the say endpoint is parse the body of the request. We can do this by using the Deno readAll method and decoding the request body to a string with a TextDecoder.

In the say file add the following lines of code:

import { ServerRequest } from '../deps.ts'

export default async (req: ServerRequest) => {
  const body = new TextDecoder().decode(
    await Deno.readAll(req.body)
  )
  console.log(body)
  req.respond({ body })
}

You may notice that we changed the function to an async function and we are using the await command to handle the promise returned from Deno.readAll, then we are decoding the data into text. We log out the text and and we respond in kind of an echo server.

Creating the cow

Now that we have the text coming in, we need to create the cow using the COWSAY module.

The COWSAY module is a third party module that is located on the deno.land site. We will need to import the module into our app. Open the deps.ts file and lets add the following:

export { say } from 'https://deno.land/x/cowsay@1.1/mod.ts'

And in the api/say.ts file lets add the following code:

import { ServerRequest, say } from '../deps.ts'

export default async (req: ServerRequest) => {
  const body = new TextDecoder().decode(
    await Deno.readAll(req.body)
  )
  console.log(say({ text: "hello" }))
  req.respond({ body })
}

We import the say command from deps.ts

And we add a log command to confirm that the say command is working.

Convert body text to JS Object

Now we need to convert the body text to a JS Object using the JSON parse method.

import { ServerRequest, say } from '../deps.ts'

export default async (req: ServerRequest) => {
  try {
    const body = new TextDecoder().decode(
      await Deno.readAll(req.body)
    )
    const cow = say(JSON.parse(body))
    req.respond({ body: cow })
  } catch (e) {
    req.respond({ 
      status: 500, 
      body: 'could not parse body' 
    })
  }
}

We use a try catch to handle any errors generated from the parsing of JSON.

Run curl command

curl -X POST localhost:3000/api/say -d '{"text": "Cows Rock!" }' -H 'Content-Type: application/json'
 ____________
< Cows Rock! >
 ------------
   \   ^__^
    \  (oo)\_______
       (__)\       )\/\
           ||----w |
           ||     ||

And you should get your cow!

Deploy

In order to deploy your api, just type vercel in the terminal and your api will build and deploy on the vercel platform!

Summary

In this article, we took a look at both Vercel and Deno, two technologies that are changing the way we deliver value to users. If you want to check out the final product online: https://cowsay.vercel.app/api

And here is the code repository: https://github.com/twilson63/cowsay