DEV Community

loading...

Top tips on Node Express (for Java Developers)

sarahk profile image Sarah Ksiyer Updated on ・12 min read

Sometimes learning a new language and stack can seem so alien, well at least it did to me. You know that programming in any paradigm is generally the same and all the rest is just syntactic sugar but it's hard to really see past that at first; especially when StackOverflow doesn't have the answers you're looking for.

Luckily, I've written a beginners guide on Node Express for you guys. I got you. If you're dabbling into new territories, looking into something new or changing stacks for a new project this blog is a sanity check for all you Java developers. So, let's get into it!
Disclaimer: I am not a Node.js expert but I learnt the hard way so you don't have to 🙇🏻‍♀️

This is a long read, here are the contents - jump ahead:

  1. 🤔 Why node? How Node works 101. Useful information to determine if Node is for your project by diving into the V8 Engine.
  2. 📦 Library package management, build management, tasks and versioning What is npm and how to get started
  3. 🖋 JavaScript nice to knows Truthy, falsy, promises, variable declarations, function/class scope, functional programming, ESLint, logging and libraries
  4. 📥📤 Node Express Architecture, folder structure, secrets and configs, middleware, ORM
  5. ⌨️ TypeScript vs JavaScript I'll let you decide

🤔 Why Node? Is it for you? Here are some things to consider...

Node (officially known as Node.js) has been a buzzword in the tech scene for a long while now. It seems like it's not going anywhere anytime soon either, so why Node?

Node is server-side JavaScript runtime built on using Chrome's V8 JavaScript Engine and it's key attraction is that its non-blocking I/O. I know... non-blocking who? Well, this will determine if Node is right for your project. In my opinion it either is or it isn't there are no in-betweens (marmite vibes).

The V8 runtime engine is single threaded and it uses an event loop to execute events in the queue. In Java the thread queue has a number of tasks awaiting execution by a thread pool right? In this case an event is a task and an event loop is the execution engine.

So what about asynchronous events, like making an API request? Does the event loop wait for the API to return? If it does latency would be an issue in a single thread. To get around this asynchronous events use callbacks.

A callback event that is added to the back of the queue with the asynchronous response. Now other events can be executed in the meantime and there's no waiting around, hence 'non-blocking I/O' 😉

Express loop diagram author sarah ksiyer
Disclaimer2: I made an error in this diagram, task is meant to read 'event'

For this reason, Node can perform faster for event-driven uses, like web servers, real-time server (that use web sockets for example), CRUD heavy apps and API's.

Also for this reason, Node doesn't perform so well on processive intensive tasks, data calculations and blocking operations.

Beyond the runtime, Node uses JavaScript and benefits from:

  • JS doesn't have a compiler so it inherently has less constraints
  • it has a flexible model which is really useful when using NoSQL
  • its platform independent (then again so is Java, so JS doesn't win any brownie points here)
  • you can use the same language on the server-side as you do on the client-side, great for those working on the full stack

Now we know what Node is, when we should use it and what the runtime brings we can get into package/build management, JavaScript basics, Node Express architecture and TypeScript.

📦 Library package/build management, tasks and versioning

If you're familiar with npm you may want to jump ahead to the next section.

npm is comparable to Maven and Gradle. npmjs.com is an online catalog of JavaScript Libraries. npm (Nodes Package Manager) manages dependencies, package information and run tasks (like build, start or run tests).

To use npm, you would need to install both node and npm and use it through its CLI. Get started here.

Each npm project has a package.json in the root project folder. This file defines the project name, version, author, description, license, dependencies and much more. Project dependencies are downloaded into the root node_modules folder.

Dependencies

There are two types of dependencies, project dependency and devDependencies which is only required in development. For example a library CLI may only be required for developers, perhaps? I would compare this aspect of npm to Maven.

Tasks

Typically your node application you should have at least a start, test and build tasks - but you can have as many as you'd like. These would be run by your continuous integration pipeline I would compare this aspect of npm to Gradle.

🖋JavaScript nice to knows

If you're familiar with JavaScript you may want to jump ahead to the next section.

 Loose objects

JavaScript, although it can be considered to have object-orientation it isn't really typed (if this is a deal breaker I'd recommend looking into TypeScript).

All class properties are regarded as optional and so are function params.

function updateUserData(userObject){
  const { id, username, email } = userObject;
  //we cannot guarantee any of these properties were passed 
  //as part of this object or if any param was passed at all
}

Truthy and falsy

These two are good to keep in mind when starting to write JavaScript code, it's something that still catches me out.

  • Truthy: Is whenever an expression is considered to return "true" which can be evaluated by 3 criterias,
    • it's not false (duh!)
    • the object is not nil (undefined or null)
    • it's not an empty object, for example an empty string ''
    Mozilla explain this pretty well.

  • Falsy: Is whenever an expression is considered to return "false" by being the inverse of the 3 above. again Mozilla explain it really well.

For example, what does if(0) evaluate to?

let name = 'Sarah';

//good!
if(name){ 
  //checks if name is not empty, undefined or null
  //line below can be accessed
  console.log('Sarah exists!');
}


let balance = 0.0;

//bad!
if(balance){ 
  //javascript considers 0 as bitwise false 
  //we expect that if balance is not undefined or null this line will be executed
  //welcome to falsy!
  console.log('This will not get printed');
}

Functional Programming

If you're familiar with Java 8+ functional programming, JavaScript would be all the much easier. Here are a few things to note:

  • Array.prototype: You do not require a Stream to transform a collection into a functional type. You can chain functional programming operations to any array because you get it for free with Array.prototype. See Mozillas documentation these functions. Unlike Java there is no "terminal operation". You can manipulate that collection again and again and again, unlike Streams.

  • .map() .flatMap() .filter() .reduce(): These collection operators are the same as a Java. Some include .sort() is comparable to Java's .sorted() and .find() is comparable to Java's .findAny()

  • Method references: There are no method references in JavaScript

  • Arrow functions: Lambdas in Java are comparable to JavaScript's arrow function, caveat is instead of ->, its =>. JavaScript doesn't care if its a Consumer or a Supplier or a Function.

In Java

Consumer<String> printString = str -> System.out.print(str);
printString.apply("Hello World!");

In JavaScript

const printString = s => console.log(s);
printString('Hello World!');

Files

Casing

The standard is that JS files are cased using kebab-case. Class names are PascalCase. Variables are camelCase. To be clear, if you have a string utils class for example, the class name should be StringUtils and the file name should be something like string-utils.js

File names

File names do not have to correlate to the class name. Sometimes a files name will have its function within it prefixed and suffixed with a dot. For example a user REST controller class could be named something like user.controller.js.

var vs const vs let

Variables are defined using var, const or let with ES6.

  • var's scope is not limited by the code block it was defined in. Very different to Java.
  • let's scope is limited to its code block and it is a variable
  • const's scope is limited to its code block and it is a constant (like final modifier)
for(var i=0; i < 10; i++){
  var forMessage = 'hello';
  let forLetMessage = ' world';
  const forConstMessage = '!!';
}

console.log(forMessage); //hello
console.log(forLetMessage); //Error
console.log(forConstMessage); //Error

Class/Function scope

Unlike Java, JavaScript:

  • is a procedural language, if you attempt to use a function above its definition, it is out of scope
  • file names can be anything
  • many "public" classes can reside in one file (do not do this however its not great practise)
  • there are no packages, and no public, private, protected or default access modifiers
  • for a class to be public it must be exported
  • functions can be exported on their own
////////////////////////////////////////////
// Using EC5 exports
// file utils-one.js
const stringTrimmer = (str) => {
  return str.trim();
}

class StringUtil {
...
}

export.module = { StringUtil, stringTrimmer };

////////////////////////////////////////////
// OR using ES6 exports
// file utils-two.js
export function stringTrimmer(str) {
  return str.trim();
}

export class StringUtil {
...
}

////////////////////////////////////////////
// Function scope
//

printString('Hello'); // Bad, not accessible

printString(str){
  console.log(str);
}

printString('Hello'); // Good, accessible

Promises

Promises are asynchronous objects that it promises to callback with a result, which is either resolved or rejected.

A great analogy for Promises is making an order at a restaurant. The single thread worker is the waiter and the order is the task.

Restaurant gif
Shout out to Jo Franchetti' medium post of that awesome GIF

You can make an order with the waiter, they will then place that order and in the meantime take another orders. When your order is complete, the waiter bring it to you when they're next free. Note the waiter did not wait at the counter until the order was complete.

The resolve or rejection values are returned into two functions,
.then() and .catch() respectively.

  • .then is when a callback was successful, the parameters in the arrow function (lambda expression) is the return from the asynchronous call, in this case it was your completed order.
  • .catch is when the callback was rejected, an error was thrown. The param passed into the arrow function here is the error that was thrown.
Kitchen.orders('donuts')
.then((completedOrder) => console.log('Bon appetit'))
.catch((error) => console.error('Sorry, we're all out. Can I offer you a muffin?');

Logging

There are many great JavaScript logger libraries to use, to list a few of the popular ones; Winston, Morgan or log4js. Each of these have transport capabilities, logs can transport to console, a file or a cloud logger like Amazon CloudWatch or LogDNA.

ESLint

Although JavaScript isn't compiled you can run static code analysis tool for identifying problematic patterns with ESLint. Unlike a compiler, ESLint has rules that are configurable and loaded into builds or IDE.

I like AirBnB's ESLinter, its fairly comprehensive and thorough. It holds me to write to a good and consistent standard. To get started use npm to install the ESLinter or read the rules on their GitHub (packed with good, bad and very bad examples, it's pretty cool).

Libraries

Finally here are a few libraries that are a must have in any JavaScript project:

  • moment: A lightweight JavaScript date library for parsing, validating, manipulating, and formatting dates.
  • lodash: Lodash (previously underscore) is a JavaScript library which provides utility functions for common programming tasks using the functional programming paradigm.
  • axios: Axios is a promise-based HTTP client that works both in the browser and in a node environment.

📥📤 Node Express

We've already discussed Node but what is Express? Express is a web application framework for Node, it is designed for building web applications and APIs. It's popularity is based on its simplicity and ease of use. (Emphasis on the simplicity, the Spring ecosystem offers Security and Access Decision Voters that Express does not)

Architecture

I've found the 3 layer architecture to be the cleanest way to separate the concerns following the SOLID principles.

  • Controller: this layer holds logic for API router endpoints, middleware like user management (authorisation, authentication, user session), domain access and controller (return response object, headers, status).
  • Service: the business logic should only live in this layer
  • Data Access: database models

Alt Text

Folder structure (without test)

File name examples (for user)
user.controller.js
user.service.js
user.model.js
user.dto.js
user.router.js

assets
└───i18n                # Translation .json files 
config                  
src
└───api                 # Express API logic
    └───controller      # Only should access Controller if authenticated and authorised
    └───middleware      # Auth, authorisation
    └───routers         # Endpoints 
└───data                
    └───dao             # DB queries
    └───entities        # Database models (entity)
└───dto                 # Data Transfer Objects
└───jobs                # Jobs like CRON
└───loaders             # Startup processes from app.js
└───services            # Business logic only lives here
└───subscribers         # Event handlers for async task
└───interfaces          # **Type declaration for DTO and Models files for Typescript
 │   app.js           # App entry point
 |   package.json       # npm tasks and dependencies
 |   .env           # Env vars and secrets

Middleware

The middleware intercepts an API routing logic with some function(s). Middleware is where you would typically handle authentication, authorisation, parent child relationship and controller.

Request middleware response flow author Sarah Ksiyer

Middleware ordering is important, the logic that is chained can break at any point. I would advise authentication comes first for obvious reasons.

//for all routes with /api will go through auth()
app.use('/api', auth())

function auth(req, res, next){
   //req is the API request object
   //res is the API response object, could be used to throw unauthorised
  // some logic..
  next();
  //next is used when things are good move on to the next middleware
}

For authentication I'd recommend looking into JWT (JSON Web Tokens). For password hash and salting I would highly recommend bcrypt.

 Secrets and configs

Secrets are stored in the root .env file. This is accessed via process
E.g: .env file:

PORT=8080
DB_DATABASE=my_db
DB_USER=sa
DB_PASSWORD=P4s5w0r1)

Environment loader:

////////////////////////
// Environment Loader
// env.loader.js
const dotenv = require('dotenv');
dotenv.config();

export default {
  port: process.env.PORT || 3000,
  database: {
    name: process.env.DB_DATABASE,
    user: process.env.DB_USER,
    password: process.env.DB_PASSWORD
  }
}

ORM - Object Relational Mapping

The two most popular ORMs are Sequelize and TypeORM. Sequelize is a JavaScript library that can be used by TypeScript as well. TypeORM is purely a TypeScript that heavily uses annotations on entities (models). TypeORM is closer to Hibernate with JPA style approach.

Both ORM's support a wide variety of database dialects from RDBMS to NoSQL.

However you may be pleased to hear that unlike Hibernate, these ORM also handle migrations. Thats right, you no longer require another framework like FlywayDB or Liquibase for migrations. It's nice to have this all in one place.

Both have great documentation and support, which you chose will depend on your coding style. Which leads me to my next and final topic...

⌨️ TypeScript vs JavaScript

So far we've discussed JavaScript, Node, Express, Architecture and ORMs... you may be thinking what else is there?

To summarise JavaScript is a great scripting language, especially with ECMAScript latest versions. However, JavaScript is a procedural language, it isn't compiled so errors are usually found at runtime, its has a loose un-typed models that make hard to ensure functions/classes are used as intended and its missing access modifiers that help use of basic design patterns like the singleton pattern.

This is where TypeScript comes to the rescue. TS which was developed by Microsoft is a scripting language, that addresses these very problems. Since JavaScript isn't a compiled language, TypeScript is transformed into JavaScript through transpilation (simplicity its referred to as compilation so I will too).

So what does TypeScript have that JavaScript does not?

  • It is typed
  • It isn't procedural
  • It has a "compiler" that tells you before runtime if something is wrong
  • Classes & Interfaces can have optional/required properties to keep benefits of a flexible model
  • Function parameters can be optional/required also
  • It has access modifiers, annotations, abstraction and interfaces
  • It supports dependency injections

With that said you can see that TS is heavily influenced by C# so naturally it feels so familiar... and thats the problem.

So far I've tried to remain impartial however heres my 2 pence. In my opinion to really benefit from what TypeScript offers you need to understand what JavaScript offers first. Starting with TS from the jump will lead you writing solutions in a completely OOP way. Which misses the point of using a lighter server-side scripting runtime like Node.

TypeScript brings a lot to the table on top of what JavaScript already does.

Discussion

pic
Editor guide
Collapse
neetu291 profile image
NP29

Great post Sarah! Very informative.

Collapse
sarahk profile image
Sarah Ksiyer Author

Neetu!!!! ❤❤❤
Thank you so much girlie

Collapse
petroskoulianos profile image
Petros Koulianos

Great post Sarah !!!!

Collapse
sarahk profile image
Sarah Ksiyer Author

Thank you so much for your supporting comment, its my first blog post 😄