DEV Community

Cover image for Simple REST API with Deno
Mohamed Amine Griche
Mohamed Amine Griche

Posted on • Updated on

Deno REST API Simple REST API with Deno

Deno's logo

After 2 years of its first release, Deno v1.0 is finally here.

So, what is Deno anyway ?

Deno is a simple, modern and secure runtime for JavaScript and TypeScript that uses V8 and is built in Rust. Deno's official website

​ Ryan Dahl the original creator of Node.js (the popular server-side JavaScript runtime) announced Deno at JSConf EU 2018 on is his talk titled "10 Things I Regret About Node.js" ( From the title you can see where are we heading to ). In another word, if you are familiar with Node.js then Deno is just like that. Except that it's improved in many ways, it's created from bottom to top to be a better implementation of Node.js.

Simple comparison with Node.js

Since Deno and Node.js serve the same purpose, it's possible to compare the two directly.

Node Deno
Engine V8 V8
Written in C++ & JavaScript Rust & Typescript
Package managing package managers: npm uses URLs
Importing pacakges CommonJS syntax ES Modules
Security full access permissioned access
TypeScript support not built in built in

A Deno tutorial

Let's play a little bit with Deno.

Deno's Installation

To install Deno just copy and paste this command line in your terminal.

curl -fsSL https://deno.land/x/install/install.sh | sh
Enter fullscreen mode Exit fullscreen mode

For more details check Deno's official installation guide.

When it's done, you will have access to the deno command.

You can run deno --help to get the list of the options and subcommands provided by Deno, and you can run deno <subcommand> --help to get additional informations specific the subcommand for example: deno run --help

Now you can run deno or deno repl to open the REPL (Read-Execute-Print-Loop) and start typing some Javascript

$ deno repl
Deno 1.0.0
exit using ctrl+d or close()
> console.log("Hello World !")
Hello World !
Enter fullscreen mode Exit fullscreen mode

For the mean time (depending on when you're reading this) on May 14 2020 the REPL supports only Javascript:

There are many things we can do to improve the REPL without introducing the TS compiler. We should do those things first (e.g. improve inspect, tab completion). Once we're on par with Node's REPL, we can start looking into how to go beyond that by using typescript. Ryan Dahl's comment on TypeScript support in REPL

Run a Deno app

Use deno run <entry-point> command to launch a Deno app.

The entry point can be .js file, .ts file or even a URL that points to an app entry point and Deno will download, compile and then run it for you:

for example: deno run https://deno.land/std/examples/welcome.ts

$ deno run https://deno.land/std/examples/welcome.ts
Download https://deno.land/std/examples/welcome.ts
Compile https://deno.land/std/examples/welcome.ts
Welcome to Deno 🦕
Enter fullscreen mode Exit fullscreen mode

If you run the program again, it gets compiled directly it does not need to be downloaded again, it's now cached by Deno:

$ deno run https://deno.land/std/examples/welcome.ts
Compile https://deno.land/std/examples/welcome.ts
Welcome to Deno 🦕
Enter fullscreen mode Exit fullscreen mode

You can force a reload of the original source with the --reload flag

deno run --reload https://deno.land/std/examples/welcome.ts

And If you open the https://deno.land/std/examples/welcome.ts URL with the browser, you'll get the page that contains the code and it's documentation.

The Deno website provides other examples in the examples page.

Let's build a simple REST API

We are going to use oak framework and Typescript (you can also use Javascript if you want to)

Let's start by creating an app.ts file that imports the Application from https://deno.land/x/oak/mod.ts and the router from './router.ts'

import { Application } from 'https://deno.land/x/oak/mod.ts'
import router from './routes.ts'
Enter fullscreen mode Exit fullscreen mode

then we need to set the environment variables HOST and PORT

const HOST = '127.0.0.1'
const PORT = 7700
Enter fullscreen mode Exit fullscreen mode

Now let's create the routes.ts file import Router from https://deno.land/x/oak/mod.ts and create the routes.

import { Router }from 'https://deno.land/x/oak/mod.ts'
import { getBooks, getBook, addBook, updateBook, deleteBook } from './controller.ts'

const router = new Router()
router.get('/books', getBooks)
      .get('/books/:isbn', getBook)
      .post('/books', addBook)
      .put('/books/:isbn', updateBook)
      .delete('/books/:isbn', deleteBook)

export default router
Enter fullscreen mode Exit fullscreen mode

NOTE: the functions getBooks, getBook, addBook, updateBook and deleteBook are imported from a local file, it's just we haven't created them yet.

After creating the router, let's finish our app.ts by adding this code to it,

const app = new Application()

app.use(router.routes())
app.use(router.allowedMethods())

console.log(`Listening on port ${PORT} ...`)
await app.listen(`${HOST}:${PORT}`)
Enter fullscreen mode Exit fullscreen mode

Now, let's create the controller.ts file and define an interface for a book IBook, then we declare an initial array of book objects:

interface IBook {
  isbn: string;
  author: string;
  title: string;
}

let books: Array<IBook> = [{
  isbn: "1",
  author: "Robin Wieruch",
  title: "The Road to React",
},{
  isbn: "2",
  author: "Kyle Simpson",
  title: "You Don't Know JS: Scope & Closures",
},{
  isbn: "3",
  author: "Andreas A. Antonopoulos",
  title: "Mastering Bitcoin",
}]
Enter fullscreen mode Exit fullscreen mode

Now let's implement the functions one by one

getBooks: returns all the books on the list.

const getBooks = ({ response }: { response: any }) => { 
  response.body = books 
}
Enter fullscreen mode Exit fullscreen mode

getBooks

getBook: returns a single book by its isbn, or error message if not found.

const getBook = ({ params, response }: { params: { isbn: string }; response: any }) => {
  const book: IBook | undefined = searchBookByIsbn(params.isbn)
  if (book) {
    response.status = 200
    response.body = book
  } else {
    response.status = 404
    response.body = { message: `Book not found.` }
  }   
}
Enter fullscreen mode Exit fullscreen mode

getBook

addBook: add a book to the books list.

const addBook = async ({ request, response }: { request: any; response: any }) => {
  const body = await request.body()
  const book: IBook = body.value  
  books.push(book)
  response.body = { message: 'OK' }
  response.status = 200
}
Enter fullscreen mode Exit fullscreen mode

addBook

afterAddBook

updateBook: updates a book if exists, return error message if not.

const updateBook = async ({ params, request, response }: { params: { isbn: string }; request: any; response: any }) => {
  let book: IBook | undefined = searchBookByIsbn(params.isbn)
  if (book) {
    const body = await request.body()
    const updateInfos: { author?: string; title?: string } = body.value
    book = { ...book, ...updateInfos}
    books = [...books.filter(book => book.isbn !== params.isbn), book]
    response.status = 200
    response.body = { message: 'OK' }
  } else {
    response.status = 404
    response.body = { message: `Book not found` }
  }  
}

Enter fullscreen mode Exit fullscreen mode

updateBook

afterUpdateBook

deleteBook: deletes a book from the books list.

const deleteBook = ({ params, response }: { params: { isbn: string }; response: any }) => {
  books = books.filter(book => book.isbn !== params.isbn)
  response.body = { message: 'OK' }
  response.status = 200
}
Enter fullscreen mode Exit fullscreen mode

deleteBook

afterDeleteBook

After adding the functions to the controller.ts we need to export them so we can use them in the router.ts file

/* return the book if found and undefined if not */
const searchBookByIsbn = (isbn: string): ( IBook | undefined ) => books.filter(book => book.isbn === isbn )[0]

export { getBooks, getBook, addBook, updateBook, deleteBook }
Enter fullscreen mode Exit fullscreen mode

NOTE: the searchBookByIsbn() it's just a helper function.

Here is the source code on github

You want more of Deno ?

Here is some resources: Deno's official website, The API documentation and you can find a lot more resources, libraries and frameworks in the Deno's awesome list.

Top comments (17)

Collapse
 
ug02fast profile image
Arthur Zhuk • Edited

Thanks for the simple proof-of-concept. How are you liking Deno so far? Do you see yourself using it in the future over Node?

Don't forget the import in app.ts

import router from './routes.ts'
Collapse
 
am77 profile image
Mohamed Amine Griche • Edited

I like it, and I love the built in typescript support, do I see myself using it in the future over node? maybe in the future.
Thank you

Collapse
 
hagopj13 profile image
Hagop Jamkojian • Edited

Thanks for the article, it was really helpful.
Taking this as a starting point, I created a similar Deno project that stores the data in MongoDB.
Have a look at it and let me know what you think: github.com/hagopj13/deno-books-api

Collapse
 
jlomaka profile image
Jlomaka

any, really ? Why then do you use TS, it would be better to write to JS right away, since there is no minute to go in and read which interface to use in request and response. For those who are also confused by this, here are the interface to import for these parameters:

import {Request, Response} from "https://deno.land/x/oak/mod.ts";
Enter fullscreen mode Exit fullscreen mode

Typing was not invented to simply ignore it, it was created to exclude a very large number of bugs at the time of writing before writing tests.

Collapse
 
am77 profile image
Mohamed Amine Griche

I totally agree with you and I am sorry for the bad practices in the code.
This was not supposed to be full fledged, secure, scalable app, it is just a simple demonstration on how you can create a simple deno REST, as simple as that.

Collapse
 
lukethacoder profile image
Luke Secomb

Great article.

Might need to add those last few lines to the controller.ts file.

/* return the book if found and undefined if not */
const searchBookByIsbn = (isbn: string): ( IBook | undefined ) => books.filter(book => book.isbn === isbn )[0]

export { getBooks, getBook, addBook, updateBook, deleteBook }
Collapse
 
am77 profile image
Mohamed Amine Griche

Ah yes, Thank you.

Collapse
 
rudy750 profile image
Rudy Sarmiento

Hey,
you also left out:

const app = new Application()

app.use(router.routes())
app.use(router.allowedMethods())

console.log(`Listening on port ${PORT} ...`)
await app.listen(`${HOST}:${PORT}`)
Collapse
 
am77 profile image
Mohamed Amine Griche

Thank you.

Collapse
 
paulotumba profile image
PauloTumba

const getBook = ({ params, response }: { params: { isbn: string }; response: any }) => {
const book: IBook | undefined = searchBookByIsbn(params.isbn)
let indece:any;

indece=params.isbn
let index=indece-1

if (book) {

response.status = 200
response.body = books[index]

} else {
response.status = 404
response.body = { message: Book not found. }
}

}

i made some changes, on get by id it was returning only the first register

Collapse
 
cescquintero profile image
Francisco Quintero 🇨🇴

Writing TS code is mandatory or optional in Deno?

Collapse
 
rudy750 profile image
Rudy Sarmiento
Collapse
 
peterstorm profile image
Peter Storm

I'm a TS noob, but a Haskell intermediate. Is it really not possible to give types to request and response? Is there a library for that?

Great guide though!

Collapse
 
kvraamkey profile image
Ramki A

Great article. @vincent Mancini
May i know which tool you're for API testing.
Thanks

Collapse
 
am77 profile image
Mohamed Amine Griche

I used "Servistate HTTP Editor & REST API Client" a chrome extension.

Collapse
 
parth_kharecha profile image
Parth Kharecha

Thank you Vincent Mancini for example
I have tested this example ane add new small changes
here is the git repository link: github.com/parthkharecha/denojs-ap...

Collapse
 
teraaret profile image
teraaret

There is error in getBook method:

response.body = books[0]

must be:

response.body = book