JavaScript - from front-end to full stack development

Moving from front-end to backend development raised many questions in my mind. What is backend development, and why do I need to learn it? Will I be able to master it?

Junior developers usually start their career tinkering with front-end libraries, like Vue.JS, for example, before deciding they are moving to something more challenging to grasp, like Node.JS.
Front-end developers deal more with visual elements and static code analysis. They handle more with how the site looks to the visitors, and hence their programming language knowledge doesn't necessitate anything other than JavaScript.
The idea behind "full-stack development" is to practically connect developers with the whole exchange dynamics between both front- and backends. Front-end is only a presentation layer, and all the dynamic data displayed on it comes from the backend.
Full-stack developers are precious experts since they understand every aspect of the system they are building and can solve complex problems that involve multiple variables.
Now that we know the eagle-eye perspective full-stack developers have, let's find out what it takes to make a transition from front-end site development into the backend.

Node.js is a JavaScript runtime environment, built on Chrome's V8 JavaScript engine. Being asynchronous and event-driven, Node.js is designed to build scalable network applications.
I know that is a lot to digest but I will break it all that down in smaller pieces, so it becomes easier to understand.
Let's see the progression of an advancing front-end developer who decided to take upon Node.JS. I choose to use a demo project since anything other than that might prove overcomplicated and undoubtedly reduce the learning effect from this exercise.

Why Node.js is a runtime environment?

Before Node.js, JavaScript could only run in a web browser for tasks like DOM manipulation, handling user events on a webpage or sending requests to a web server. But in 2009, Ryan Dahl released Node.js by combining the event loop, the fastest JavaScript engine V8 and C++.
Node is an executable file in virtually any environment. It operates as a standalone application and can work independently of the browser. Anything that takes place on the server can be handled by Node, including tasks such as user requests or database queries.

Event-driven Node.js

Node.js allows developers to use event-driven programming architecture for specific tasks.

const writer = require("getWritableStreamSomehow");
writer.write("hello, Node.js \n");
writer.on("finish", () = > {
  console.log("All writes are now complete.");
});

In the example above, the getWritableStreamSomehow is not a real module or library. It is only used to demonstrate the idea behind event-driven programming in Node.js.
The writer module has an "on" function is used to handle some event on the writable stream. Once the writer function finishes outputting the "hello …" message, it will fire the "finish" event. The "on" function will listen and wait for it to finish. A callback function is then triggered, and the console prints: "All writes are now complete."

The asynchronous nature of Node.js

The asynchronous nature of Node.js manifests in the fact that our written code does not have to wait for long-running tasks to complete. It can continue to run the remainder of our code and run the callback assigned to our long-running task when it completes.

const foo=()=>{
  someLongRunningFunction("some Parameter",()=>{
    // code to run when the task completes.
    console.log("long running function completed");
  });
  // do some other task.
  console.log("some other task completed");
}
foo(); 

In the above example, when the execution reaches the long-running function, the code written below the function will not have to wait for it to complete, but it will run and the message "some other task completed" will print. Some examples of long-running functions include executing a database query, writing or reading a large file from the file system, or making requests to other servers. The message "long-running task completed" will only print when that task is completed. This is the main reason for the performance of Node.js. It can handle other user requests asynchronously without waiting for some long-running I/O task to complete.
Now that you know the basic idea behind Node.js, let us get started with setting up Node.js for development.

Setting up Node.js for development

You can download the latest stable version of Node from this link and install it. After installing Node.js, open your terminal (cmd in windows and bash for mac). Type "node" and hit "enter". If a welcome message appears with the installed Node version, we are good to go.

Now we need a code editor to write our code. Personally, I prefer Visual Studio Code for development. It offers smooth performance, a lightweight code editor, and plenty of extensions for increased productivity. Use the link below to download and install the Visual Studio Code or any other IDE of your preference.

In this guide I will be developing a CRUD (stands for Create – Read – Update - Delete) application with Node.js. First, I will start by setting up the files for our project.
We'll start with the basics, and then continue along. The end result will be a fully working API, exposing our todoApp Application.

  • Create a new folder in the desired directory with any name of your choice. I will choose todoApp.
  • Open VS code and open the todoApp project folder.
  • Open your terminal and browse to the current working directory
  • Type "npminit" and hit enter, and it will ask you a series of questions. Preferably answer or skip them by hitting enter.

The above procedure will create a package.json file in the todoApp directory. Create a new folder named "src". Within the "src" folder, create an app.js file.

Requiring/Importing modules

In any framework, we need to import helper libraries and modules. Node.js modules are simple JavaScript files that expose functions, interfaces, or classes to use in our code.
In Node.js, importing modules is required because it uses the 'require' function to do so. For reference, here is a list of all native modules supported by Node.js.

In the app.js file, I will be importing the 'fs’ module, also known as the file system module. This module allows us to perform tasks related to the host computers’ file system.
To avoid moving forward and backward, please create a second folder under todoApp called 'data'. This is where we will write our data file into later.

Take a look at the code below:

const fs = require("fs"); 
data = { name: "Sveti", age: "31" };
fs.writeFile("./data/data.json", JSON.stringify(data), (err) => {
if(!err){
    console.log("data written to file successfully");
  } 
});

The 'require' function uses the name of a module. It passes as a parameter. 'Require' practically imports functionality from the library into our source program and allows us to use the methods therein.

Writing to a file

In the first line, I defined a constant named 'fs' and I assigned it the fs module's contents returned by the 'require' function. Now all the File System module methods are available to me on the fs constant as an object.

Next, I am writing a simple JSON data object to a file named data.json to the 'data' folder we created previously. The fs.writeFile function can take the following arguments:

  1. Path to file (type String).
  2. Data to be written (type String), please read follow this documentation for more info.
  3. Options (Mainly filesystem related), optional
  4. The callback will be called when the writing process completes. The callback takes a single argument of type err and is also optional.

Please note: With Node.js, you should always use asynchronous functions, as you will get the best performance.

If the file exists at the given path, the fs.writeFile function will overwrite it. If the file does not exist, a new file with the same name will be created, and write the data passed through the second parameter.

Please note that I am passing the JSON object after stringifying it because it will be written as an 'Object' without it.
After writing the data, when the callback executes, it will return an error if occurred or return null. I will handle the error using a simple 'if' statement. To run the above code, type 'node src/app.js' in the terminal and hit enter.
We get the output as “data written to a file successfully.Yessss! We ran our first Node.js code successfully. Now let’s check the file data.json in the data folder. Just open the data.json file, and take a look!

Reading from a file

const fs = require("fs");
fs.readFile("./data/data.json", { encoding: "utf-8" }, (err, data) => {
  if (!err) {
    console.log(JSON.parse(data));
    return;
  }
  console.log(err);
});

I wrote some data in the data.json file; let's read it now. I will be using the fs.readFile method. The fs.readFile function takes the following three arguments.

  1. Path to file (type String)
  2. Options such as encoding; buffer is used by default
  3. Finally, the callback will be called when the reading process completes. The callback takes two arguments: error and data.

Please note that I am parsing the data returned to us to convert the previously written stringified data to a JSON object.
Let’s run our code. Now I get output on the terminal as the same data is written previously:

{ name: "Sveti", age: "31" }

Exporting from a file

Suppose I am building a web API for a todo app where the client app sends 'http requests' to perform CRUD operations on their todo's. But I want to validate data sent to my server so that my database or storage doesn't get filled with impractical data. I can create a function that validates inputs from my users.
I could create this function in our main app.js file, but it is good practice always to shift code to separate files to keep the codebase manageable.
I will create a new folder named 'utils' in my todoApp folder and add a new file called 'validator.js'.
Now let me create a new function named 'validator'. I will be validating user inputs for length only because users may add time for todos, which will include alphanumerical characters and symbols, e.g., "Take a walk at 8:00".

const validator = (userInput) => {
  return userInput.length > 5;
};

If the user gives a todo with less than five characters, this function will return 'false'.

Here comes the part when I will export 'validator', to use in our main app.js file.

There are three main syntaxes to export functions:

const validator = (userInput) => {
  return userInput.length > 5;
}; 
module.exports={validator};
const validator = (userInput) => { 
  return userInput.length > 5; 
}; 
module.exports.validator=validator; 
//module.exports.anyNameYouWant=exactNameOfFunction 
module.exports.validator = (userInput) => { 
  return userInput.length > 5; 
};

I personally prefer the first syntax. After exporting, I will import this function in app.js.

const { validator } = require("../utils/validator.js"); 
console.log(validator("run"));

This is the same 'require' function I used previously to import the ‘fs’ module, but this time I need to provide the complete path to the validator.js file, as it is not a module or library provided by Node itself.
Another thing to note is that I am restructuring the return value from the required function as {validator} to avoid importing unnecessary functions.
Remember: For external modules and files you wish to import, you must require them with the absolute or relative path.
To test our validator function, I will be passing our function with a test input to console.log to print some output on the terminal.

const { validator } = require("../utils/validator.js"); 
console.log(validator("run"));

Output

false

Now let’s test for correct input.

const { validator } = require("../utils/validator.js"); 
console.log(validator("run at 9"));

Output

true

So, the validator function is working just fine.
Now that we know how to import external functionality to an application, and briefly looked into creating an external function, in which we exported a method, I will now be creating a web server with Express.

The web server with Express.js

I will be creating a web server to handle web requests from my client. I can make a server using the Node.js native HTTP module too, but Express.js simply minimizes a lot of code, provides a clean code structure, and is super impressive. It is simple to configure and is the most popular framework of Node.js to create Restful services.
First, I need to install it by typing, 'npmi express', and hitting enter on the command line. After installing it, I will import it in our app.js. Setting up a server in Express requires only three steps. Don’t believe me? Take a look:

const { validator } = require("../utils/validator.js");
const express = require("express");
const server = express();
server.listen(4000, () => {
  console.log("express server is up and running");
});
  1. First, I imported Express. The require function with the express() parameter returns a function, and it is assigned to the 'express' constant.
  2. Then I simply called the express() function and assign it to a constant called 'server' (you can name it any way you want, but I personally like this setup). Once express boots up, a fully functional HTTP server is brought up.
  3. Now all the server functions are available on the server constant, and it can listen to requests port 4000, ready for client requests. The 'listen' function takes two parameters, the port to listen to, and a call back function called when the server starts listening.

Let us run our code now by typing on the command line:

node src/app.js

After executing the above command, a message is displayed, confirming that our express server is up and running, but the code execution does not stop.
Can you guess why? That is because the server is running continuously, and this is what a client expects. I can stop the server by merely hitting Ctrl+C.

Rerun the server and test by sending a request from your browser. In the search bar of the browser, type: localhost:4000 and hit enter.
Initially, your browser window will display an error message saying, "cannot GET /".
Why is that? Even though my server is listening on port 4000, it cannot handle any GET requests on the clients’ behalf yet.

Please use this link to get further information about http requests. The only thing sure at this point, we need to write a couple of additional lines of code.

GET request

GET request is used to get data from a server.

const { validator } = require("../utils/validator.js");
const express = require("express");
const server = express();
let todos = [ 
  { todo: "take a walk at 9" },
  { todo: "Appointment with dentist" },
];
server.get("/todos", (req, res) => {
  res.status(200).send(todos);
});
server.listen(4000, () => {
  console.log("express server is up and running");
});

I defined an array of todos in the above code and filled it with two sample todo objects. The new thing certain, I am handling a user GET request by 'get' function that is available on the server constant. All HTTP request handling methods in Express.js takes two main arguments.

  1. The path (or request URI) on the HTTP method of type String
  2. A callback function that takes two arguments as request and response - the Request object is used to check what data sent to the server, and the Responseobject is used to send some data as a response to the client.

In this GET /todos request, I only need to know the path of the request. That is why only the response object is used to send the response. Later, I will use the request object as needed.
The send method on the response object is used to send a response back to the client, and it only takes a single argument, which is the data I want to return. We'll also need to send the response status code back so that the client knows that the request was successful, in this case, a 200.
First, I used the 'status' method to add status to my response and then 'send', with our todo data's actual response.
To learn more about HTTP status codes, follow this link.
Now, I will stop the express server and restart it. This will reflect the recent code changes. Let us test again; Type localhost:4000/todos in your browser and hit enter.

Your browser should now show you the logic response, a JSON Array, with our Todo data.

[{"todo":"take a walk at 9"},{"todo":"Appointment with dentist"}]

The POST request and Express middleware

The POST request is meant to send or create new data in our application. With the POST request, you can add additional data to the request body.
Before our server can handle further requests, we need to do some additional coding.
To keep it nice and tidy, create a 'crud.js' file in the utils folder, and open it for editing.

const fs = require("fs");
let todos = [];
const addTodo = (todo) => {
  todos.push(todo);
  console.log(todos);
  return new Promise((resolve, reject) => {
    fs.writeFile("./data/data.json", JSON.stringify(todos), (err) => { 
      if (err) { 
        reject(err); 
      } 
      resolve(todos.length - 1); 
    }); 
  }); 
};

Wow. It is getting complicated now. Let me explain the above piece of code.
Initially, I am importing the File System Module, as we learned previously. Next, I am setting the 'todos' variable to an empty array. We'll be filling this array dynamically later and do all kinds of operations on it.
You’ll notice I am doing some major restructuring, which will help us maintain clean code.
My addTodo method now looks complicated. It takes the 'todo' variable as parameter input. First, it pushes the new item to the end of the 'todo' array, and then it returns a new promise in which the updated 'todo' array lives.
Part of the promise is writing the contents of the todos array to the file, just as we did previously. Or not exactly?

We are also equipping our array with a numerical index, so each entry holds an entry ID. This allows easier operations on our data when we need to start updating or deleting them at a later stage of our app.
As we just created the crud.js file, let's use it in our main app.js file and add a POST request handler.
Before we are going to write any other code, we need a tool to work with APIs. I am going to download and install Postman to test my POST request. After installing, launch Postman.

1. Let’s create a new requests collection and name it todo.

Postman Configuration

2. Now click the option button beside todo collection and add a request with name post todo.

Postman Configuration

3. Choose post from the drop-down and in the search bar and type localhost:4000/todo

Postman Configuration

Now that Postman is all set up and ready, let’s move back, write some code.
For the GET request our API only needs to know the path of the request. However, we need to parse the body of the request into a JSON object for the POST request.
To parse the request body, we need to add a middleware that will parse JSON's request body and pass it to the designated request handler. Middleware is an intermediate function which gets called before reaching the specified function. Middleware functions have access to both request and response objects. To add a middleware in Express, we need to pass a function to the server's 'use' method.

const { validator } = require("../utils/validator.js"); 
const express = require("express"); 
const server = express(); 
server.use(express.json());

The 'use' function is used to add middleware functions on an Express server. The middleware function passed to the 'use' function gets called before reaching a designated request handler, the POST request handler. The express.json function will get called on the user's POST request. Before going to the POST request handler, express.json will parse the request body to JSON and pass it to the POST request handler. Let's handle the POST request now.

const { addTodo } = require("../utils/crud.js");
server.post("/todo", async (req, res) => { 
  let { todo } = req.body;
  if (!validator(todo)) {
    res.status(400).send("Error! Your todo cannot be saved"); 
    return; 
  }
  let todoID = await addTodo(todo);
  res.status(200).send({
    id: todoID, 
    msg: "Your todo has been saved successfully!", 
  });
});

Notice that I am passing an async callback in my POST request because I need to use 'await' here. As we have already set the express.json middleware, the body from the POST request is ready to be used, and I am destructing it as a todo array.

First, I am validating the todo. Any validation error is sent back to the client. The todo is written to the data.json file using the addTodo method I previously created on successful validation. The index of the todo returned is sent to the client with a success message. We again need to restart the server.

Note: Restarting an Express Server can be frustrating and annoying. Please install another utility called Nodemon, which will automatically restart our server whenever it detects a change of our node javascript source files.

Type following line of the command line,

npm i nodemon --save-dev

and hit enter. The --save-dev flag will install nodemon as a development dependency. Now I will shut down the server for the last time and rerun it by typing:

nodemon src/app.js

My server will restart now, and it will do so every time I save one of my source files.
Let’s test the POST handler, to add todo on a post request body, we need to click on the body tab on Postman and then choose JSON as shown.

Postman Configuration

I will be adding a sample todo on the request by typing,

{
  "todo":"Do a good run at the park"
}

Once you add the above JSON code, hit 'Send'. To check the server response, scroll down a bit.

{
  "id": 1,
  "msg": "Your todo has been saved successfully!"
}

The server is correctly handling our request.

Refactoring GET request

Let’s write a readTodos method in our crud.js file.

const readTodos = () => { 
  return new Promise((resolve, reject) => { 
    fs.readFile("./data/data.json", { encoding: "utf-8" }, (err, data) => { 
      if (err) { 
        reject(err); 
      } 
      todos = JSON.parse(data); 
      resolve(todos); 
    }); 
  }); 
};

The readTodos method returns a promise in which the todos are read from the data.json file. I did this previously, but now I am storing my To-Dos, which I read from the file to a 'todos' array; Eventually, this same array is needed later when I want to add a Todo and store it.
The readTodos method needs to be made available in our app.js file, and consecutively we will refactor the GET todos handler.

const { addTodo, readTodos } = require("../utils/crud.js"); 
const { validator } = require("../utils/validator.js"); 
const express = require("express"); 
const server = express(); 
server.use(express.json()); 
server.get("/todos", async (req, res) => { 
  try { 
    let todos = await readTodos(); 
    res.status(200).send(todos); 
  } catch (error) { 
    res.status(400).send(error); 
  } 
});

Like in the POST request, I am using async await for cleaner code, and the resolved todos are sent back to the client. Save the file, and make a GET request from Postman, and you should receive the below results back on screen.

[ 
  "Do a good run at the park" 
]

DELETE Request

As the name suggests a delete request is used to remove data on the server. Once we can add To-Dos, and read them, we should be able to delete a To-Do when needed. Therefore, we will create a 'deleteTodo' method in our crud.js file.

const deleteTodo = (id) => {
  todos.splice(id, 1);
  console.log(todos);
  return new Promise((resolve, reject) => {
    fs.writeFile("./data/data.json", JSON.stringify(todos), (err) => {
      if (err) {
        reject(err);
      }
      resolve();
    });
  });
};

The 'deleteTodo' method uses the ID of the todo to be deleted.
First, I will use the splice method on todos array to remove the designated todo item, then in the returned promise, the updated todos array is written to our data file.
In our app.js we'll need some additional code to handle the DELETE request:

server.delete("/todo/:id", async (req, res) => {
  try {
    let id = req.params.id;
    await deleteTodo(id);
    res.status(200).send("Todo deleted successfully with id: " + id);
  } catch (error) {
    res.status(400).send(error);
  }
});

Notice that in the DELETE request, my path is annotated as "/todo/:id" the colon after the todo/ has a special meaning it clearly explains that the expected user input is a parameter. Let’s say "/todo/5" to delete todo with ID 5; the colon in the path gives us access to a new property on the request object called "params", which I am using here.
To read more about request objects, head over to the Express API reference found here. By now above code should be pretty self-explanatory.
We call the 'deleteTodo' method from our crud.js file, and along we pass the destructed ID, we are longing to delete. Finally, we’ll return a Success – or Error Message back to the user.
When all code is in place, test this endpoint with Postman.
Using localhost:4000/todo/0 to delete the first todo I created. What you expect as output would be:

Todo deleted successfully with id:0

Conclusion

Node.js provides developers a way to build server side applications with minimal configuration and minimal code. The use of JavaScript for both, front-end, and back-end, speeds up the application development process. Node.js is an excellent choice for agile development and prototyping. Node was designed to build scalable I/O intensive network applications. As always, there is a 'but'; don’t use it for CPU intensive tasks.

Author

Stanimira Yovcheva | Junior Software Engineer

Stanimira is a young professional looking to leave a memorable mark in the IT industry. Her endless thirst for knowledge and her focused approach to technical challenges make her a reliable engineer and a valuable team member. She converts every free minute around work sessions to study advanced software development principles and cool advanced tech, continually looking to put what she learned into good use.