loading...
Cover image for Create a Simple REST API with DENO and OAK framework

Create a Simple REST API with DENO and OAK framework

nickolasbenakis profile image Nickolas Benakis ・5 min read

About Deno

Deno has just recently released version 1.0.0 and this is a new secure runtime for JavaScript and Typescript. Main key features of Deno are:

  • Secure by default. No file, network, or environment access, unless explicitly enabled.
  • Supports TypeScript out of the box.
  • Ships only a single executable file.
  • Has built-in utilities like a dependency inspector (deno info) and a code formatter (deno fmt).
  • Has a set of reviewed (audited) standard modules that are guaranteed to work with Deno: deno.land/std
  • If you’d like to learn more about Deno, please check out Deno’s landing page.

    About Oak

    Oak is middleware framework for Deno's http server, including a router middleware. This middleware framework is inspired by Koa and middleware router inspired by koa-router. For more Info check out here

    Let's start

    I'll be installing Deno using Brew.

    brew install deno
    

    To verify if the Deno is installed or not. Just type deno --version on your terminal and it will show the install version of Deno.

    $ Deno --version
    deno 1.0.0
    v8 8.4.300
    typescript 3.9.2
    

    After the installation steps, let's create a directory for our application

    mkdir denoRestApi && cd denoRestApi
    

    We are going to develop a Crud REST api with the following structure

    - src
      - controllers
        - dinosaurs.ts
      - models
        - dinosaurs.ts
      - routes
        - routes.ts
      - types
        - types.ts
    - server.ts
    
    • Controllers : have a logic of the application and handle the client requests.
    • Models : contain the model definition.
    • Routes : containing API routes.
    • Types : contain the types used by model and application responses.
    • Server : code to run localhost server.

    Now let’s create our server.ts file in the root of our directory :

    import { Application } from "https://deno.land/x/oak/mod.ts";
    import router from "./src/routes/routes.ts";
    
    const port = 9000;
    const app = new Application();
    
    app.use(router.routes());
    app.use(router.allowedMethods());
    
    console.log(`Server running on port ${port}`);
    await app.listen({ port });
    

    The Application class wraps the serve() function from the http package. It has two methods: .use() and .listen(). Middleware is added via the .use() method and the .listen() method will start the server and start processing requests with the registered middleware.

    The middleware is processed as a stack, where each middleware function can control the flow of the response. When the middleware is called, it is passed a context and reference to the "next" method in the stack.

    Our Next step is to create our endpoints in our routes.ts :

    import { Router } from "https://deno.land/x/oak/mod.ts";
    import {
      getDinosaur,
      getDinosaurs,
      addDinosaur,
      updateDinosaur,
      deleteDinosaur,
    } from "../controllers/dinosaurs.ts";
    
    const router = new Router();
    
    router.get("/api/v1/dinosaurs", getDinosaurs)
      .get("/api/v1/dinosaurs/:id", getDinosaur)
      .post("/api/v1/dinosaurs", addDinosaur)
      .put("/api/v1/dinosaurs/:id", updateDinosaur)
      .delete("/api/v1/dinosaurs/:id", deleteDinosaur);
    
    export default router;
    

    One of TypeScript’s core principles is that type checking focuses on the shape that values have. This is sometimes called “duck typing” or “structural subtyping”. In TypeScript, interfaces fill the role of naming these types, and are a powerful way of defining contracts within your code as well as contracts with code outside of your project. See below types.ts :

     export interface Dinosaur {
       id: string;
       name: string;
       era: string;
       area: string;
       diet: string;
     }
    

    Properties inspired by here

    Now let's create our initial list with dinosaurs in models/dinosaurs.ts :

    import { Dinosaur } from "../types/types.ts";
    
    export const Dinosaurs: Array<Dinosaur> = [
      {
        id: "1",
        name: "Achillobator",
        era: "Late Cretaceous",
        area: "Mongolia",
        diet: "carnivorous",
      },
      {
        id: "2",
        name: "Agilisaurus",
        era: "Late Jurassic",
        area: "China",
        diet: "herbivorous",
      },
      {
        id: "3",
        name: "Melanorosaurus",
        era: "Late Triassic",
        area: "South Africa",
        diet: "omnivorous",
      },
    ];
    

    After we have created our Dinosaur Interface, our dinosaur list and our routes, let's continue with our methods for each endpoint. controllers/dinosaurs.ts :

    import { v4 } from "https://deno.land/std/uuid/mod.ts";
    import {
      Dinosaur,
    } from "../types/types.ts";
    import { Dinosaurs } from "../models/dinosaurs.ts";
    
    const getDinosaurs = ({ response }: { response: any }) => {
      response.body = {
        success: true,
        data: Dinosaurs,
      };
    };
    
    const getDinosaur = (
      { params, response }: { params: { id: string }; response: any },
    ) => {
      const selectedDino: Dinosaur | undefined = Dinosaurs.find((dino) =>
        dino.id === params.id
      );
      if (selectedDino) {
        response.status = 200;
        response.body = {
          success: true,
          data: selectedDino,
        };
      } else {
        response.status = 404;
        response.body = {
          success: false,
          msg: "Dinosaur Not Found",
        };
      }
    };
    
    const addDinosaur = async (
      { request, response }: { request: any; response: any },
    ) => {
      if (!request.hasBody) {
        response.status = 400;
        response.body = {
          success: false,
          msg: "No data",
        };
      } else {
        const { value : dinosaurBody } = await request.body();
        const dinosaur: Dinosaur = dinosaurBody;
        dinosaur.id = v4.generate();
        Dinosaurs.push(dinosaur);
        response.status = 201;
        response.body = {
          success: true,
          data: dinosaur,
        };
      }
    };
    
    const deleteDinosaur = (
      { params, response }: { params: { id: string }; response: any },
    ) => {
      const filteredDinosaurs: Array<Dinosaur> = Dinosaurs.filter(
        (dinosaur: Dinosaur) => (dinosaur.id !== params.id),
      );
      if (filteredDinosaurs.length === Dinosaurs.length) {
        response.status = 404;
        response.body = {
          success: false,
          msg: "Not found",
        };
      } else {
        Dinosaurs.splice(0, Dinosaurs.length);
        Dinosaurs.push(...filteredDinosaurs);
        response.status = 200;
        response.body = {
          success: true,
          msg: `Dinosaur with id ${params.id} has been deleted`,
        };
      }
    };
    
    const updateDinosaur = async (
      { params, request, response }: {
        params: { id: string };
        request: any;
        response: any;
      },
    ) => {
      const requestedDinosaur: Dinosaur | undefined = Dinosaurs.find(
        (dinosaur: Dinosaur) => dinosaur.id === params.id,
      );
      if (requestedDinosaur) {
        const { value : updatedDinosaurBody } = await request.body();
        const updatedDinosaurs: Array<Dinosaur> = Dinosaurs.map(
          (dinosaur: Dinosaur) => {
            if (dinosaur.id === params.id) {
              return {
                ...dinosaur,
                ...updatedDinosaurBody,
              };
            } else {
              return dinosaur;
            }
          },
        );
    
        Dinosaurs.splice(0, Dinosaurs.length);
        Dinosaurs.push(...updatedDinosaurs);
        response.status = 200;
        response.body = {
          success: true,
          msg: `Dinosaur id ${params.id} updated`,
        };
      } else {
        response.status = 404;
        response.body = {
          success: false,
          msg: `Not Found`,
        };
      }
    };
    
    export {
      updateDinosaur,
      deleteDinosaur,
      getDinosaurs,
      getDinosaur,
      addDinosaur,
    };
    

    Run application

    Deno  run --allow-net  server.ts
    

    Request with curl

    Resolution

    We've created a dead simple, readable rest api with few lines of code. If you noticed we did not used any Node_modules dependancies instead Deno has an amazing list of features at Standard Library and Third-Party Modules. I like Deno so far and I am very excited about the out-of-box tools it provides.

    You can find my repo here. Leave a 🌟 if you liked it.

    Thanks a lot,
    Don't hesitate to write any comments below, I would love to answer.

    Feel free to connect on:

    Posted on May 24 by:

    nickolasbenakis profile

    Nickolas Benakis

    @nickolasbenakis

    Hello 👋, I am Nick. I like to explore and write about web.

    Discussion

    markdown guide