Idiomatic JavaScript Backend. Part 1

Ciro Ivan
Ciro Ivan
Sep 2, 2018 · 6 min read

This article was originally published on dev.to

Hi everyone! This part of series Idiomatic JavaScript Backend.

Part 2/3
Part 3/3

Important Information

For best experience please clone this repo: https://github.com/k1r0s/ritley-tutorial. It contains git tags that you can use to travel through different commits to properly follow this tutorial :)

$ git tag 
1.preparing-the-env
2.connecting-a-persistance-layer
3.improving-project-structure
4.creating-entity-models
5.handling-errors
6.creating-and-managing-sessions
7.separation-of-concerns
8.everybody-concern-scalability

Go to specific tag

$ git checkout 1.preparing-the-env

Go to latest commit

See differences between tags on folder src

$ git diff 1.preparing-the-env 2.connecting-a-persistance-layer src

0.What

Hi everyone! today’s topic is about building an App with NodeJS.

What we’re gonna do? We will build an service for allowing users to:

  • create its own profile
  • create a session
  • list other users
  • edit its own user

And…

We’re going to use cURL!

Its not relevant to check, but you can click here to see the full requirements on what this app should fulfill.

Now I’m going to slowly build it from scratch!

1. Preparing the environment

Let’s do our “Hello World” with ritley to get started:

.
├── .babelrc
├── package.json
└── src
└── index.js

In this tutorial we’re going to use Babel. To do so with nodejs we need babel-node to run our app. So this is our package.json:

{
"name": "tutorial",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "babel-node src"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@ritley/core": "^0.3.3",
"@ritley/standalone-adapter": "^0.2.0",
},
"devDependencies": {
"@babel/core": "^7.0.0-beta.55",
"@babel/node": "^7.0.0-beta.55",
"@babel/plugin-proposal-class-properties": "^7.0.0-beta.55",
"@babel/plugin-proposal-decorators": "^7.0.0-beta.55",
"@babel/plugin-transform-async-to-generator": "^7.0.0-rc.1",
"@babel/preset-env": "^7.0.0-beta.55"
}
}

Why @ritley/core and @ritley/standalone-adapter ? :|

As ritley is quite small, many features are separated on different packages. As core is indeed required, standalone adapter too because we’re going to run a node server by ourselves here. If you’re on serverless environments such as firebase you can keep going without it.

This would be our .babelrc:

{
"presets": [["@babel/preset-env", {
"targets": {
"node": "current"
}
}]],
"plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties", { "loose": false }],
["@babel/plugin-transform-async-to-generator"]
]
}

And our hello world src/index.js:

import { setAdapter, AbstractResource } from "@ritley/core";
import Adapter from "@ritley/standalone-adapter";

setAdapter(Adapter, {
"port": 8080
});

class SessionResource extends AbstractResource {
constructor() {
super("/sessions");
}

get(req, res) {
res.statusCode = 200;
res.end("Hello from sessions!");
}
}

class UserResource extends AbstractResource {
constructor() {
super("/users");
}

get(req, res) {
res.statusCode = 200;
res.end("Hello from users!");
}
}

new SessionResource;
new UserResource;

In previous snippet we import standalone-adapter and we bind it to the core by calling setAdapter(<adapter> [, <options>]). This will create and bind a new HttpServer to any AbstractResource subclass. You can check how it works.

When building a ritley app you’ve to choose an adapter. That defines how requests are sent to resources.

ritley uses https://nodejs.org/api/http.html (req, res)api so probably you're quite familiar with it.

Note that we’ve created two similar classes, we could do this instead:

import { setAdapter, AbstractResource } from "@ritley/core";
import Adapter from "@ritley/standalone-adapter";

setAdapter(Adapter, {
"port": 8080
});

class DefaultResource extends AbstractResource {
get(req, res) {
res.statusCode = 200;
res.end(`Hello from ${this.$uri}`);
}
}

new DefaultResource("/sessions");
new DefaultResource("/users");

Anyways we’re going to keep it separated as both resources will start diverge quite soon.

now you can $ npm start and then run some curl commands to see if everything is working properly:

$ curl localhost:8080/users
$ curl localhost:8080/sessions

This is our first step!

2. Connecting a persistence layer

We need to have some kind of persistence layer. We’re going to install lowdb because we don’t need too much overhead for now.

Everyone favorite part: its time to install new dependencies!:

$ npm install lowdb shortid

However we need to keep in mind that any dependency, whatever we attach to our project, should be easy to replace. That’s we’re going to wrap lowdb into an interface with “CRUD alike” methods to keep things extensible.

Lets continue by implement our database.service.js using lowdb:

import low from "lowdb";
import FileAsync from "lowdb/adapters/FileAsync";
import config from "./database.config";
import shortid from "shortid";

export default class DataService {
onConnected = undefined

constructor() {
this.onConnected = low(new FileAsync(config.path, {
defaultValue: config.defaults
}))
}

create(entity, newAttributes) {
return this.onConnected.then(database =>
database
.get(entity)
.push({ uid: shortid.generate(), ...newAttributes })
.last()
.write()
)
}
}

For now we only implement create method. That's fine now.

.
└── src
├── database.config.js
├── database.service.js
├── index.js
└── lowdb.json

Our project is growing fast! We’ve created database.config.js too which contains important data that may be replaced quite often so we keep it here:

export default {
path: `${__dirname}/lowdb.json`,
defaults: { sessions: [], users: [] }
};

You can skip this paragraph if you’ve already used lowdb. Basically you need to specify the actual path of the physic location of the database, since it doesn’t need a service like other database engines. Hence lowdb is way simpler and fun to play with, though less powerful and should not be used to build enterprise projects. That’s why I’m wrapping the whole lowdb implementation on a class that exposes crud methods, because its likely to be replaced anytime.

And now, we’ve changed our src/index.js to properly connect database to controllers:

@@ -1,5 +1,6 @@
import { setAdapter, AbstractResource } from "@ritley/core";
import Adapter from "@ritley/standalone-adapter";
+import DataService from "./database.service";

setAdapter(Adapter, {
"port": 8080
@@ -17,15 +18,18 @@ class SessionResource extends AbstractResource {
}

class UserResource extends AbstractResource {
constructor() {
super("/users");
+ this.database = new DataService;
}

- get(req, res) {
- res.statusCode = 200;
- res.end("Hello from users!");
+ post(req, res) {
+ this.database.create("users", { name: "Jimmy Jazz" }).then(user => {
+ res.statusCode = 200;
+ res.end(JSON.stringify(user));
+ });
}
}

new SessionResource;
new UserResource;

We’ve changed as well our get method to a post to emulate a real case of creation request. By running this command we get back the newly created data!

$ curl -X POST localhost:8080/users

Check src/lowdb.json to see the changes!

Okay so, we just connected lowdb and run our first insertion!

3. Improving the project structure

We need to organize a bit our project.

First we’re going to arrange our folders like this:

// forthcoming examples will only show src/ folder
src/
├── config
│ ├── database.config.js
│ └── lowdb.json
├── index.js
├── resources
│ ├── session.resource.js
│ └── user.resource.js
└── services
└── database.service.js

Now lets remove a bit of code from src/index.js in order to have only the following:

import { setAdapter } from "@ritley/core";
import Adapter from "@ritley/standalone-adapter";

import SessionResource from "./resources/session.resource"
import UserResource from "./resources/user.resource"

setAdapter(Adapter, {
"port": 8080
});

new SessionResource;
new UserResource;

So basically we moved our controllers (aka resources) to a separated folder called resources.

Next is to setup Dependency Injection on src/resources/user.resource.js to be able to inject an instance of our database service.

In order to do so we’re going to install an extension package called @ritley/decorators:

$ npm install @ritley/decorators

Then, lets make a few changes on src/services/database.service.js to be exported as a singleton provider:

import config from "../config/database.config";
+import { Provider } from "@ritley/decorators";

+@Provider.singleton
export default class DataService {
onConnected = undefined

By adding @Provider.singleton we will be able to construct only one instance every time the provider gets executed. That means all classes that declare it as a dependency will share the same instance.

Lets add it to src/resources/user.resource.js:

import DataService from "../services/database.service";
+import { Dependency, ReqTransformBodySync } from "@ritley/decorators";

+@Dependency("database", DataService)
export default class UserResource extends AbstractResource {
constructor() {
super("/users");
- this.database = new DataService;
}

+ @ReqTransformBodySync
post(req, res) {
+ const payload = req.body.toJSON();
+ this.database.create("users", payload).then(user => {
- this.database.create("users", { name: "Jimmy Jazz" }).then(user => {
res.statusCode = 200;

@Dependency executes DataService (now its a provider) then receives an instance and assigns it as a named property after class local constructor gets executed.

So basically we removed complexity that involves service instantiation on controllers. I guess you’re familiar with these practices.

You may noticed that we’ve also removed hardcoded payload and we’ve placed @ReqTransformBodySync on top of the post method.

This decorator allows to access request body or payload by delaying method execution till its fully received. Like body-parser does but more explicit because you don’t need to bother yourself reading method contents to know that it requires payload to properly work, and its more pluggable since you can configure at method level.

Now try to execute this command:

$ curl -d '{ "name": "Pier Paolo Pasolini" }' localhost:8080/users

-X POST is assumed if -d (payload) is provided.

You should reveive a HTTP 200 OK response with ur new user created! Check database contents :)

That’s all for now folks! On next chapter on series we will see how ritley manages to link models with controllers, handle exceptions and manage sessions.


Originally published at dev.to.

Ciro Ivan

Written by

Ciro Ivan

Fullstack developer

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade