Deno is currently the coolest kid on the block with its type-safety and rust compilation. Deno also only has access on your machine only to what you explicitly allow it. If that sounds like something you value, let's  leave node.js behind.

Install Deno

Shell:

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

Powershell (Windows):

iwr https://deno.land/x/install/install.ps1 -useb -outf install.ps1; .\install.ps1 v0.38.0

Check if Deno is installed on your system

deno --help	

If its not you will have to add it to your PATH , more info here

https://github.com/denoland/deno_install

Ready to create our first Deno file!

touch api.ts

Deno packages are imported differently from npm

This is how to we import Deno server

import { serve } from "https://deno.land/std@v0.42.0/http/server.ts";

import { serve } from "https://deno.land/std@v0.42.0/http/server.ts";

Not interested in building an application and just wanna get up and running? Check out our example code using the 🍦 tasty! 🍦 Deno library https://crunchskills.com/deno-http-web-server-example-like-express/

And SQLite for our database

import { open, save } from "<https://deno.land/x/sqlite/mod.ts>";

Hello world, we are running a server!

You should always create something simple in the beginning like this to test integration. Consider putting it on your destination server to test as well.

const PORT = 8162;
const s = serve({ port: PORT });

console.log(` Listening on <http://localhost>:${PORT}/`);

for await (const req of s) {
  req.respond({ body: "Hello World\\n" });
}

Now run your application with

Deno api —allow-net

Allow net flag allows Deno to network access

This is another difference between node and a strength of Deno. It only gets access to what you allow it to.

And visit localhost:8162

You should see your page served!

Want interview questions delivered to your inbox?

Subscribe

Get CrunchSkills in your inbox weekly. Stay sharp for your next technical interview.

Building blocks of an API

Lets play further around with the request string

for await (const req of s) {
  const url = req.url;

  req.respond({ body: `Hi there, accessing from ${url}` });
}

Now you can visit any url inside of your website try it

localhost:8162/hello/world

The req.url variable is what we will all of our paths on.

Lets split it on question mark to give us URL params and the actual request URL. That will allow us to have things like count of records the api is supposed to return from the database.

const params = req.url.split("?");

lets quickly check if there's something weird with the request

 if (params.length > 2) {
    req.respond(bad_request);
    continue;
  }

This means the user specified more than one question mark, we wont support that so we will send the bad request response

We are in a for loop, so just continue to skip all the other code in this iteration after we send the error response

Lets declare it above so its faster to error the request later.

 const bad_request = { body: "Error 400 bad request.", status: 400 };

Lets get the search parameters using deno inbuilt urlSearchParams class

const url = params[0];
const search_params = new URLSearchParams(params[1]);

The database

Now we are ready to start adding to our database

on top of the file lets simply open the database file

 const db = await open("jobboard.db");

This means now Deno will need access to writing into files. Now we need to start it with a --allow-write flag as well.

Deno api --allow-net --allow-write

We will be using our API to get job postings as well as add them.

So lets create those tables if they don't exist simply with

db.query(
  "CREATE TABLE IF NOT EXISTS jobs (id INTEGER PRIMARY KEY AUTOINCREMENT, created_at DATETIME, last_updated DATETIME , active BOOLEAN, company_id INTEGER, apply_url TEXT, job_title CHARACTER(140), details TEXT, pay_range TEXT)"
);
db.query(
  "CREATE TABLE IF NOT EXISTS companies (id INTEGER PRIMARY KEY AUTOINCREMENT,  logo_url TEXT, name CHARACTER(50), description TEXT, created_at DATETIME, last_updated DATETIME , active BOOLEAN)"
);

The API

Now we have our database setup

Lets create a path to view and a path to add job postings

doing a simple if statement on the url inside the request response loop

 if (url == "/api/v1/jobs") {
}

Then check if for count parameter inside the html query. And if it is, check if its higher than 100. Which is the max records i want to allow users to query at a time.

let count = 10; // base  
let request_count = search_params.get("count");
  if (request_count) {
    // enforcing max 100 record request
    if (parseInt(request_count) > 100) {
      req.respond(bad_request);
      continue;
    } else {
      count = parseInt(request_count);
    }
  }

Query the database for job_title and name of the company by joining two of our tables on the company id. Since a company can have multiple job postings, and a job posting can have only one company, we keep them in separate tables.

for (const [
      job_title,
      name,
    ] of db.query(
      "SELECT job_title,name FROM jobs JOIN companies ON company_id = companies.id ORDER BY jobs.id DESC LIMIT ?",
      [count]
    )) {
      results.push({ company_name: name, job_title: job_title });
    }

And respond to the request

req.respond({
      body: JSON.stringify(results),
      status: 200,
    });
    continue;

Code of the whole route :

if (url == "/api/v1/jobs") {
    // base values
    let count = 10; // 10 records

    // safe type get count param
    let request_count = search_params.get("count");
    if (request_count) {
      // enforcing max 100 record request
      if (parseInt(request_count) > 100) {
        req.respond(bad_request);
        continue;
      } else {
        count = parseInt(request_count);
      }
    }
    const results = [];
    for (const [
      job_title,
      name,
    ] of db.query(
      "SELECT job_title,name FROM jobs JOIN companies ON company_id = companies.id ORDER BY jobs.id DESC LIMIT ?",
      [count]
    )) {
      results.push({ company_name: name, job_title: job_title });
    }
    req.respond({
      body: JSON.stringify(results),
      status: 200,
    });
    continue;
  }

Now its time to implement a password protected route to add jobs to the jobs-board

 if (url == "/api/v1/jobs/add") {
}

We can quickly hard code a password and make it more complex later, inside the Deno file itself. If the user provided ?pw URL param we check if its valid

let password_valid = false; // initialize to false
  const password = "supersecurepassword"; // set our pasword
  let request_password = search_params.get("pw"); // check the search param pw if its a valid password
  if (request_password) {
    if (request_password == password) {
      password_valid = true;
    } 
  }

If its not we respond "Not Allowed" and close the connection.

 if (password_valid == false) {
      req.respond({
        body: "Not Allowed",
        status: 405,
      });
      continue;
    }

Now that we identified the validity we can safely add the records from provided params.
Note - There is no way to know the ID of the company without query the company table with the name of it.

const apply_url = search_params.get("apply_url");
      const job_title = search_params.get("job_title");
      const company = search_params.get("company");
      const details = search_params.get("details");
      const pay_range = search_params.get("pay_range");
      let company_id;
      // To get the company ID we need to know if from the other table
      for (const [id] of db.query("SELECT id FROM companies WHERE name = ?", [
        company,
      ]))
        company_id = id;
			// companies are added in a different endpoint
      if (!company_id) {
        req.respond({ body: "Company Name not specified.", status: 400 });
        continue;
      }
			// write into database
      db.query(
        "INSERT INTO jobs (company_id, apply_url, job_title, details, pay_range,created_at,last_updated,active) VALUES (?,?,?,?,?,?,?,?)",
        [company_id, apply_url, job_title, details, pay_range, time, time, 1]
      );
      req.respond({
        status: 200,
      });

Code of the whole password protected path

if (url == "/api/v1/jobs/add") {
    let password_valid = false;
    const password = "supesecuredpassword";
    let request_password = search_params.get("pw");
    if (request_password) {
      if (request_password == password) {
        password_valid = true;
      }
    }
    if (password_valid == false) {
      req.respond({
        body: "Not Allowed",
        status: 405,
      });
      continue;
    }
    // validated can add
    else {
      const apply_url = search_params.get("apply_url");
      const job_title = search_params.get("job_title");
      const company = search_params.get("company");
      const details = search_params.get("details");
      const pay_range = search_params.get("pay_range");
      let company_id;
      // To get the company ID we need to know if from the other table
      for (const [id] of db.query("SELECT id FROM companies WHERE name = ?", [
        company,
      ]))
        company_id = id;
      // companies are added in a different endpoint
      if (!company_id) {
        req.respond({ body: "Company Name not specified.", status: 400 });
        continue;
      }
      // write into database
      db.query(
        "INSERT INTO jobs (company_id, apply_url, job_title, details, pay_range,created_at,last_updated,active) VALUES (?,?,?,?,?,?,?,?)",
        [company_id, apply_url, job_title, details, pay_range, time, time, 1]
      );
      req.respond({
        status: 200,
      });
			continue;
    }
  }

API done!

simply close the database at the end of the file

await save(db);
db.close()

Having the ability to create password protected paths and get paths and access to all query strings you can create any kind of API you want. Sky is the limit.

The whole API put together

import { serve } from "https://deno.land/std@v0.42.0/http/server.ts";
import { open, save } from "https://deno.land/x/sqlite/mod.ts";

const PORT = 8162;

const db = await open("jobboard.db");
db.query(
  "CREATE TABLE IF NOT EXISTS jobs (id INTEGER PRIMARY KEY AUTOINCREMENT, created_at DATETIME, last_updated DATETIME , active BOOLEAN, company_id INTEGER, apply_url TEXT, job_title CHARACTER(140), details TEXT, pay_range TEXT)"
);
db.query(
  "CREATE TABLE IF NOT EXISTS companies (id INTEGER PRIMARY KEY AUTOINCREMENT,  logo_url TEXT, name CHARACTER(50), description TEXT, created_at DATETIME, last_updated DATETIME , active BOOLEAN)"
);

const time = new Date().getTime();

const s = serve({ port: PORT });

const bad_request = { body: "Error 400 bad request.", status: 400 };
console.log(` Listening on http://localhost:${PORT}/`);

for await (const req of s) {
  //split the request url
  const params = req.url.split("?");
  // error out on more than one ?
  if (params.length > 2) {
    req.respond(bad_request);
    continue;
  }

  // before ? is base url
  const url = params[0];
  // after the ? theres params
  const search_params = new URLSearchParams(params[1]);

  // get endpoint of jobs
  if (url == "/api/v1/jobs") {
    // base values
    let count = 10; // 10 records

    // safe type get count param
    let request_count = search_params.get("count");
    if (request_count) {
      // enforcing max 100 record request
      if (parseInt(request_count) > 100) {
        req.respond(bad_request);
        continue;
      } else {
        count = parseInt(request_count);
      }
    }
    const results = [];
    for (const [
      job_title,
      name,
    ] of db.query(
      "SELECT job_title,name FROM jobs JOIN companies ON company_id = companies.id ORDER BY jobs.id DESC LIMIT ?",
      [count]
    )) {
      results.push({ company_name: name, job_title: job_title });
    }
    req.respond({
      body: JSON.stringify(results),
      status: 200,
    });
    continue;
  }
  //write endpoint
  if (url == "/api/v1/jobs/add") {
    let password_valid = false;
    const password = "supersecurepassword";
    let request_password = search_params.get("pw");
    if (request_password) {
      if (request_password == password) {
        password_valid = true;
      }
    }
    if (password_valid == false) {
      req.respond({
        body: "Not Allowed",
        status: 405,
      });
      continue;
    }
    // validated can add
    else {
      const apply_url = search_params.get("apply_url");
      const job_title = search_params.get("job_title");
      const company = search_params.get("company");
      const details = search_params.get("details");
      const pay_range = search_params.get("pay_range");
      let company_id;
      // To get the company ID we need to know if from the other table
      for (const [id] of db.query("SELECT id FROM companies WHERE name = ?", [
        company,
      ]))
        company_id = id;
      // companies are added in a different endpoint
      if (!company_id) {
        req.respond({ body: "Company Name not specified.", status: 400 });
        continue;
      }
      // write into database
      db.query(
        "INSERT INTO jobs (company_id, apply_url, job_title, details, pay_range,created_at,last_updated,active) VALUES (?,?,?,?,?,?,?,?)",
        [company_id, apply_url, job_title, details, pay_range, time, time, 1]
      );
      req.respond({
        status: 200,
      });
      continue;
    }
  }
}
// Save and close connection
await save(db);
db.close();

Want interview questions delivered to your inbox?

Subscribe

Get CrunchSkills in your inbox weekly. Stay sharp for your next technical interview.