π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦
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;
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.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.-
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 folderOn 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. 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.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.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.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.
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.
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
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 }); |
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; |
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; | |
} | |
} |
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; | |
} | |
}; |
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 | |
} | |
}; |
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; |
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 | |
}; |
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
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
- - 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
- 10 Things I Regret About Node.js β Ryan Dahl β JSConf
- Oak Framework
- Deno β MongoDB Driver
- 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
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.
This is Revision of my Medium Article
Until then, signing off for the day.
Happy Learning. :)
Top comments (8)
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 !
My bad.
Thank you for the feedback, I will rectify that :)
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.
Are you certain about this? As far as I learnt, it comes into plugins.
Not 100%, but certain enough to create an account to post a message about itπ
Thanks for pointing out. I will look into that and rectify the mistake.
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
Thanks.