DEV Community

Cover image for API with Deno : : Antidote for Node
Shravan Kumar B
Shravan Kumar B

Posted on β€’ Edited on

API with Deno : : Antidote for Node

πŸ¦•πŸ¦•πŸ¦•πŸ¦•πŸ¦•πŸ¦•πŸ¦•πŸ¦•πŸ¦•πŸ¦•πŸ¦•πŸ¦•πŸ¦•πŸ¦•πŸ¦•πŸ¦•πŸ¦•πŸ¦•πŸ¦•πŸ¦•πŸ¦•πŸ¦•πŸ¦•πŸ¦•πŸ¦•πŸ¦•

Alt Text

Since Deno 1.0 released

Disclaimer

To start with, the framework I am currently using OAK/Snowlight is to write a RESTful API which is currently stable Deno version 1.0.0

There are several speculations and hypothesis amongst Developers as follows saying,

Is Deno going to replace Node?
Is Deno better than Node?
Is all the amount of efforts, time and energy put on learning Node completely pointless?


As Ryan Dahl claimed in JSConf in his talk 10 Things I Regret About Node.js

β€œ Node could have been much nicer ”

When Ryan started out Node; he missed out on very essential aspects which he recalled in his speech delivered in 10 Things I Regret About Node.js

To summarize those design flaws in the Node.js that Ryan had mentioned are as follows;

  1. Security: Node has no security. Since you use NPM packages and you don’t completely know what’s in that code; maybe these codes may have unfound loopholes or severe vulnerabilities making network attacks or hacks easier. Easy access to the computer was very much open.
    Deno overcame this security concern by putting the Deno environment in a sandbox, by default, where each operation beyond the execution context was permitted explicitly by the user.

  2. Importing URLs: One major change was requiring modules from the folder node_modules where Node.js used syntax omitting the extensions in files, caused issues with the browser standards. To resolve this issue, they had bundle up the Module Resolution Algorithm to find the requested module in Node.
    To overcome this, Deno came up with the use of import instead of the require. You did not have the packages locally; instead, you could fill it in with URL from which you need the module from. This throws the light on another aspect; explained in the next point.

  3. The unnecessary need of node_modules: With Node, NPM packages had too many codebases whose vulnerabilities were not surely known. Apart from that, every time you need to use a module from node_modules; you had require it; which would have to again run Module Resolution Algorithm which itself is quite complex.
    In Deno, there was no need for the node_modules folder. Modules are imported using URLs; which are cached and used for the project you are executing available globally. This might make you wonder; does it always need an internet connection to run?
    Well, no. When packages are initially imported; they are downloaded and cached, just like how it works with NPM. They are cached in a folder

    On Linux : $XDG_CACHE_HOME/deno or $HOME/.cache/deno
    On Windows: %LOCALAPPDATA%/deno (%LOCALAPPDATA% = FOLDERID_LocalAppData)
    Another file called .deno_plugins is also generated in the project’s directory itself in the first run when you use certain packages like deno_mongo to install the rust binaries of the same.

  4. package.json: With the above two major drawbacks; maintaining package.json was an unnecessary abstraction. Semantic Versioning was one of the main purposes that package.json served.
    On the contrary, Deno does not support the use of a package manager like npm. Hence, the need for Semantic Versioning is eliminated, eliminating the need for package.json like the manifest.

  5. Handling Asynchronous Operations: In Node, the initial evolution of handling asynchronous operations was, using Callbacks Pattern. As time passed, it evolved using the Promise API in the early versions of v8; which were included in late 2009 and removed in early 2010. There was an outbreak since by then, there were several packages/libraries which used Callback patterns for async operations. Node was designed much before Javascript had Callbacks / Promises API.
    In Deno, the most fundamental or let us say lowest level binding Promises API is β€œops” binding to handle Asynchronous Operations.

  6. Out of the box, TypeScript Compiler Built in: Node supports JavaScript scripts, with .js files. If we had to write TypeScript in Node Environment; we had to set up the TypeScript Configuration for the project along with the TypeScript package.
    This pain of set up is over with Deno, which gives right away without the initial configuration of the application. The use is confined to default configurations of the Deno’s TypeScript Compiler. Anyhow, if your want to override the default configuration, you can add the β€˜tsconfig.json’ file; using flag β€˜- -config=tsconfig.json’.
    Normal JS also works too with Deno; basically even files with .js extensions.

  7. Lastly, the usage of the await supported by v8 - Top level Async: Node supported the async-await pattern of handling asynchronous operation after the release of ES5/ES6. If you are defining a function that does some asynchronous operation, then you will have to use this standard pattern of async-await.
    Deno had the awesome feature of using await directly since it was binded directly to the promises. In simpler terms, you could use β€˜await’ without using the async keyword in a program.


alt text

With these flaws around, and each of them being handled in Deno; Deno looks quite promising.
Yet need to see how this environment and frameworks built on Deno, based on their adoption rate and flexibility, will see how Deno turns the industry around.


Alt Text

In this article, I will be discussing about an Application Server setup using Oak Framework connected to MongoDB database using deno_mongo native Deno Driver.

Let us dig into Deno and then start with Creating a RESTful API using Deno [ Oak Framework β€” Inspired by Koa Framework ].

What is this Deno?

  • Simple, Modern & Secure Runtime for JavaScript and TypeScript that uses v8 engine built using Rust.
  • Recently in May 2020, v1.0.0 of Deno was out officially.
  • Deno is built with Rust in the core.
  • Supports TypeScript without explicit setup.
  • Not compatible with node modules and npm

Further details can be found in the official Deno v1.

Now starting with creating simple RESTful API using Deno’s framework called Oak.

In this article, we will be creating Application Server using

Oak: A Middleware Framework for Deno’s HTTP server; inspired by Koa Framework.

deno_mongo: It is a MongoDB database driver built for the Deno Platform. A native database driver for MongoDB.

To get started, before starting to build the application, this is a simple application to build an Application Server, to create a user and fetch user details.

Below given is the folder structure of the mini-project as follows

  • models contains the model definition, in our case only User Interface

  • routers contains API routes to handle API Requests
    controllers will be holding the files that deal with validation of the data, whatever that is been sent from the frontend.

  • services contain all the business logic of the API routes.

  • repository contains the files that deal with all the queries related to the database.

  • middlewares contains the files that have different route-level middlewares

  • helpers contains files that deal with some sort of helper functions

  • keys contain files that store the .json/.js/.ts file to store constant values or key values

  • .deno_plugins upon first execution; this folder is generated, just cached version of the libraries or modules that imported in the codebase.

  • app.ts is the entry point of the applications

Alt Text


Starting with an β€˜app.ts’ file.

import { Application } from "https://deno.land/x/oak/mod.ts";
import router from "./routers/userRoute.ts";
const port = 3001;
const app = new Application();
//Logger
app.use(async (ctx, next) => {
await next();
const rt = ctx.response.headers.get("X-Response-Time");
console.log(`${ctx.request.method} ${ctx.request.url} - ${rt}`);
});
// Timing
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
ctx.response.headers.set("X-Response-Time", `${ms}ms`);
});
app.use(router.routes());
app.use(router.allowedMethods());
console.log(`Server running on port ${port}`);
await app.listen({ port });
view raw app.ts hosted with ❀ by GitHub

This is app.ts file; starting point.


Now we have a routers folder, which has a collection of routes related to the same service.
Here let us say, User as an independent service.

Now let us create a router for User which has HTTP methods
POST β†’ β€˜/user’
GET β†’ β€˜/user/:id’

To add a user along with getting user data as well. The route file would like this as follows:
Create a β€˜routers’ folder and create another file β€˜userRoute.js’. This file deals only with routing to the user service.

import { Router } from "https://deno.land/x/oak/mod.ts";
import { getUserDetails, createUser } from "../controllers/userController.ts";
// import { simpleMiddleware } from "../middlewares/testMiddleware.ts";
const router = new Router();
router.get("/user/:id", getUserDetails);
router.post("/user", createUser);
export default router;
view raw userRoute.ts hosted with ❀ by GitHub

This is userRoute.ts file;


Next, create another folder controllers having a file userController.js, which completely deals with handling Success Response and Error Response and apart from which usually deals with Data Validation.

import { createUserService, getUserService } from "../services/userService.ts";
import { RouterContext } from 'https://deno.land/x/oak/mod.ts';
import { responseEnvelope, errorEnvelope } from "../helpers/response.ts";
export const createUser = async (contxt: RouterContext) => {
try {
let response = contxt.response;
let body = await contxt.request.body();
let serviceCall = await createUserService(body.value);
response.body = await responseEnvelope(201,serviceCall);
response.status = 201
} catch (e) {
contxt.response.body = await errorEnvelope(500, e);
contxt.response.status = 500;
}
}
export const getUserDetails = async (contxt: RouterContext) => {
try {
let response = contxt.response;
let serviceCall = await getUserService(contxt.params);
response.body = await responseEnvelope(200, serviceCall);
response.status = 200;
} catch (e) {
contxt.response.body = errorEnvelope(500, e);
contxt.response.status = 500;
}
}
view raw userController.ts hosted with ❀ by GitHub

This is userController.ts file;


Following up create services folder having another file userServices.ts which completely handles the business logic of the API.

import { getUserById, createUser } from './../repository/userDAL.ts';
export const createUserService = async (body: any) => {
let newUser = await createUser(body);
return newUser;
};
export const getUserService = async (params: any) => {
try {
let userData = await getUserById(params.id);
return userData;
} catch (e) {
throw e;
}
};
view raw userServices.ts hosted with ❀ by GitHub

This is userServices.ts file; having business logic.


Finally, comes the Repository layer which deals with database queries. Basically following DRY ( Do not Repeat Yourself ); write those queries once in Repository Layer and can be called multiple times as required.

import Database from '../database/config.ts';
const User: any = new Database('user');
export const createUser = async (data: any) => {
let result: any = await User.collection.insertOne(data);
return result;
};
export const getUserById = async (query: any) => {
try {
return await User.collection.find();
} catch (e) {
throw e
}
};
view raw userDAL.ts hosted with ❀ by GitHub

This is userDAL.ts file


Following we create a class β€˜database’ for the database connectivity whose object we can use to create an instance of, to write queries.

Create a database folder, with a file β€˜config.ts’, which looks like as follows,

import { MongoClient } from "https://deno.land/x/mongo@v0.8.0/mod.ts";
import { URI, dbName } from "../keys/appConstants.ts";
class Database {
/**
* Database Connectivity
*/
//Fields
client: any;
db: any;
collection: any;
constructor(collectionName: string) {
const client = new MongoClient();
client.connectWithUri(URI);
this.db = client.database(dbName);
this.collection = this.db.collection(collectionName);
}
}
export default Database;
view raw config.js hosted with ❀ by GitHub

This is the config.ts file; dealing with all the database connectivity code.


Finally creating a User Interface, a model for user database; since we do not have an ORM currently for Deno; creating an Interface;

In a model folder, create a file userInterface.ts;

/**
* Create an Interface to describe structure of User JSON
*/
export interface IUser {
name: string,
age: number,
dob: Date
};
view raw userInterface.ts hosted with ❀ by GitHub

This is userModel.ts; having User Interface.



These are the fundamentals requirements needed to run the Oak Framework based Server Application.

Along with this, there are other pieces of code snippets that will be required to run the code as well. These are available in my Github account.

If you want to clone the project that I am working on, clone oak branch.

git clone -b β€˜oak’ https://github.com/shravan20/deno-crud-api.git
Enter fullscreen mode Exit fullscreen mode

Now let’s run the project. Open the terminal/command prompt in the root directory of the project

> deno run --allow-net --allow-write --allow-read --allow-plugin --unstable app.ts
Enter fullscreen mode Exit fullscreen mode
  • - allow-write - -allow-net; are the flags required to give permission to Deno to access network and other resources. When you run this command for the first time; it will download all the required library files and put them in cache locally in a folder named ./.deno_plugins; which we basically put in .gitignore before committing your code.

Resources

  1. 10 Things I Regret About Node.js β€” Ryan Dahl β€” JSConf
  2. Oak Framework
  3. Deno β€” MongoDB Driver
  4. Deno is the New Way of JavaScript-Ryan Dahl & Kitson Kelly

Since we are at the very start of the Deno.land and the current scenario looks as though it has a very promising scope for the future. Looking forward to work on the coming frameworks in the Deno environment.

I am already fond of another called Snowlight Framework ( Inspired by Express Framework in Node); which is also available in the GitHub codebase in the β€˜SnowLight’ branch.

git clone -b β€˜snowlight’ https://github.com/shravan20/deno-crud-api.git
Enter fullscreen mode Exit fullscreen mode

Deno looks like already better than Node as per my point of view. Looking forward to exploring many more frameworks and libraries in the Deno platform.

Alt Text

This is Revision of my Medium Article

Until then, signing off for the day.
Happy Learning. :)

Top comments (8)

Collapse
 
agborkowski profile image
AgBorkowski β€’

there is an issue with sources for paragraph
"In a model folder, create a file userInterface.ts;"
bellow should be the source from that file
/github.com/shravan20/deno-crud-api...
now is model repeat instead of an interface

nice art thankyou !

Collapse
 
shravan20 profile image
Shravan Kumar B β€’

My bad.
Thank you for the feedback, I will rectify that :)

Collapse
 
gewoonwoutje profile image
gewoonwoutje β€’

If I understand it correctly, the .deno_plugins folder is actually created by deno_mongo to install the rust binaries of this plugin. Deno caches it's files globally in the .deno folder in the user's home directory.

Collapse
 
shravan20 profile image
Shravan Kumar B β€’

Are you certain about this? As far as I learnt, it comes into plugins.

Collapse
 
gewoonwoutje profile image
gewoonwoutje β€’

Not 100%, but certain enough to create an account to post a message about itπŸ˜…

Thread Thread
 
shravan20 profile image
Shravan Kumar B β€’

Thanks for pointing out. I will look into that and rectify the mistake.

Collapse
 
rexagod profile image
Pranshu Srivastava β€’ β€’ Edited

Nice post! I just wanted to point out that node now supports Top-Level Await as well. It's behind a experimental flag though.

See github.com/nodejs/node/pull/30370

Collapse
 
shravan20 profile image
Shravan Kumar B β€’

Thanks.