JavaScript 's Top Advanced Principles
Just learning a technology/programming language doesn’t make sense in a long run. Learning to use it to solve something specific makes all its sense.
⚡️ Get inspired by open-source projects I’ve built over the past years on GitHub 🚀
Introduction
I decided to write this book while I was looking for a backend developer role in Australia.
I came to Australia in January 2020, and after three months of travelling around the whole country, I started to apply for jobs in my field (software engineering).
However, the country was hit by the COVID-19 pandemic and I found it a bit hard to find the right job in a company that was matching the values of both the employer and myself.
Since then, I have been really enjoying making things while learning, and I ultimately thought that writing a book to help me be a better developer would help me reinforce my knowledge and give a great resource for anyone who wants to become a better developer. This book will then upgrade your skills to become a remarkable and irreplaceable software engineer!
This manual assumes you already have good JavaScript knowledge. It covers the best practices and isn’t a learning book that goes from the fundamentals of the language. If you have little knowledge of JavaScript, just close it until you are fluent with the language (so you will be able to take advantage of everything mentioned in this book).
I really hope this book will make your career and skills so much better, making you a remarkable JS engineer so that no one can ignore you.
SOLID Principles
To start directly with the best, we will learn the S.O.L.I.D principles.
SOLID was popularized by Robert C. Martin (known as Uncle Bob).
Single Responsibility
“A class should have one and only one reason to change. Use dependency injection to achieve single responsibility.”
A single responsibility is when a class doesn’t depend of others to work. It should just do one thing. A class should have one, and ONLY ONE reason to change.
Dependency injection helps to having a single responsibility class.
Open/Closed Principle
“Software entities should be open for extension, but closed for modification.”
This principle says “A software module (it can be a class or method) should be open for extension but closed for modification.”
But how to avoid modifying our code?
Well, we need somehow to abstract common behaviors.
To create an abstraction, we can use abstract classes or interfaces. In our case, these two classes are not supposed to be of one type. Instead they have only a common behavior or a role, which can be extracted to an interface.
The factory pattern can also be used to archive this.
This principle is also the one that will save you the most of your time during the development in the future, when adding new features. Use interfaces to archive it. Limit as much as possible the number of methods in a class. If there are too many methods, it will be difficult to change the behavior of the class.
Liskov Substitution
“Child classes must respect parent class contracts. Input parameters, return values, and exceptions must be consistent. Violating this principle breaks polymorphism.”
The Liskov Substitution Principle emphasizes respecting a contract (or interface) regardless of the underlying implementation, whether it’s interfaces, classes, or inheritance hierarchies.
When the output of a method deviates from the contract (interface), it constitutes a violation of the principle.
Child classes should never override the type definitions of their parent classes.
The returned type should align with the declared type in the interface, even if the language doesn’t support explicit type declarations or enforces them.
Input Parameters
The first rule of Liskov Substitution applies to the parameters of the overriding methods. The child method should either have the same or more input parameters as the parent method. Furthermore, we should consider the data types of the parameters. They should either be the same or more generic than those of the parent method.
Return values
The second rule of Liskov Substitution applies to the return values of the overridden method. The types of the returned values of the overridden method should either be the same or more specific than those returned by the same method in the parent class.
Exceptions
The last rule of Liskov Substitution involves throwing exceptions in the parent class and its child classes. Overridden methods in child classes should throw the same more specialized exceptions that can be thrown in the parent class.
Interface Segregation
“Do not force classes to implement unused methods. Small, focused interfaces are preferable.”
We should never be forced/required to use methods we don’t need.
Otherwise, it becomes a fat interface, which is the opposite of “Interface Segregation”
Remember, an interface with only one method is totally fine.
Dependency Inversion
“High-level modules should depend on abstractions, not concrete implementations. Decouple code wherever possible.”
You have to decouple the code.
High-level code should NEVER depend on low-level ones.
Instead, it should depend on wider abstractions.
High-level code should never depend on low-level code. In other words, one class should never be forced to depend on specific implementations of any kinds.
High level model depends on abstractions.
Design Principles
Principle of Least Knowledge (AKA ‘Law of Demeter’)
This is more of a guideline than a law, but still interesting and important.
Tell, Don’t Ask
Tell an object to do something rather than rip data out of an object to do it in client code.
Don’t talk to strangers.
Each unit should have only a limited knowledge about other units: only units “closely” related to the current unit.
Each unit should only talk to its friends: don’t talk to strangers.
Only talk to your immediate friends.
The Scout Rule
“Always leave the code cleaner than you found it.”
For scout camps, if you find a mess on the ground, you clean it up regardless of who have done the mess. You intentionally improve the environment for the next group of campers.
The same apply to developers, always leave the code better and cleaner than you initially found.
The broken window theory
“Fix issues immediately. Bad code left unchecked becomes the new normal.”
Broken window principle teaches you that leaving “broken windows’’ (bad designs, wrong decisions, or poor code) unrepaired will just make the situation worse and worse.
Fixing each flaw as soon as it is discovered will prevent this to be a “normality”.
Rule Of Three
“Duplicate code twice before extracting it into a reusable function. Avoid premature refactoring.”
This rule can help us in making a decision when to extract a duplicated code. It says: Extract duplication only when you see it for the third time.
1. The first time you do something, you just write the code.
2. The second time you do a similar thing, you duplicate your code.
3. The third time you do something similar, you can extract it and refactor.
But why? Why should we duplicate our code, when we have always been said that it is wrong?
These simple steps are built to prevent you from prematurely and wrong refactoring, when the problem we are trying to solve is not yet clear. Prematurely refactoring a piece of code when we don’t exactly see the problem behind it can cause tremendous problems in the future.
These rules also prevent you from wasting your time and making something reusable when at the end of the day you end up with only one use of it.
GRASP
“Assign responsibilities wisely using: Controller, Creator, Indirection, Information Expert, Low Coupling, High Cohesion, Polymorphism, Protected Variations, Pure Fabrication.”
GRASP, often associated with the SOLID pattern, stands for General Responsibility Assignment Software Patterns. It is used to assign responsibilities for various modules of code. GRASP employs different roles, including:
Controller
Creator
Indirection
Information expert
Low coupling
High cohesion
Polymorphism
Protected variations/classes
Pure fabrication
All these patterns solve some software problem common to many software development projects.
Even if GRASP provides a guidance for OO design, it still gives you a lot of room for flexibility in your application.
Design Patterns
ADR
“A modern alternative to MVC for web architectures.”
ADR stands for “Action-Domain-Responder”. It’s usually a better pattern than MVC (Model-View-Controller) for web architecture. Request handlers are also good.
CQRS
“Separate commands (state-changing) from queries (data-fetching).”
CQRS stands for Command-Query-Responsibility-Segregation. It’s a principle of imperative computer programming. It was devised by Bertrand Meyer as part of his pioneering work on the Eiffel programming language. It states that every method should either be a command that performs an action, or a query that returns data to the caller, but not both.
Composite
“Compose objects into tree structures for part-whole hierarchies.”
When it comes to inheritance, the are actually different ways to extend a class.
Obviously, the most common and native way of doing so consists to extend a class to a parent class with the ‘extends’ keyword for most object oriented language.
However, you can also use composition for archiving the same.
The composite pattern is a partitioning design pattern and describes a group of objects that is treated the same way as a single instance of the same type of object.
The intent of a composite is to “compose” objects into tree structures to represent part-whole hierarchies.
Factory
“Factory pattern is an object responsible for creating other objects for you.”
Depending of some criteria and circumstances, it instances the right object and specify the correct arguments into the class’s constructor. Then, it returns (gives back) the object to you from the “create” method.
Factory methods
In class-based programming, the factory method pattern is a creational pattern that uses factory methods to deal with the problem of creating objects without having to specify the exact class of the object that will be created.
Abstract Factory
This pattern is a creational design pattern that lets you produce families of related objects without specifying their concrete classes.
Singleton
“Guarantees only one instance of a class. Use sparingly—can lead to global state issues.”
Singleton isn’t as popular as before in programming. Singleton pattern sometimes can also become an anti-pattern when it isn’t used in the right way. Singleton pattern guarantees to have only one instance of the given class during the application lifecycle. For instance, when you have to deal with database instances, it can be useful to have a singleton pattern because we don’t want our database to be instanced more than one time.
Sometimes, Singleton is used as a global object, however it is definitely not its purpose.
Singleton doesn’t allow dependency injection and class mocking, which represents its main disadvantages. That’s why you should avoid its usage as mush as it’s not needed.
Anti-patterns
Boat Anchor
We leave or keep code, files, and functions just for ‘record/history’ reasons, which makes the codebase difficult to understand and maintain over time.
Lava Flow
In programming jargon, the lava flow is a problem that arises when each layer of code is written by a new generation of developers, over multiple years, giving complexities.
Consequently, when the system is put into production, there’s often a need to maintain backward compatibility with the original, incomplete design.
Spaghetti Code
Like the famous spaghetti meal, everything is closely intermixed. It’s then hard to add a new feature or change a functionality in the code without refactor an entire piece of code and very likely introduce new bugs.
God Object
God Object means an object that does too much or knows too much, and breaks as well the Single Responsibility.
Poltergeist
The opposite of the God Object is the Poltergeist, which is essentially a useless or redundant class.
The primary characteristic of a Poltergeist is an object that holds any internal state and its sole responsibility is to invoke methods on other objects.
Consequently, we’ve added an unnecessary layer of abstraction to the entire application architecture, merely to relocate some code. We believe we’ve adhered to the Single Responsibility Principle and the Tell, Don’t Ask Principle, but instead, we’ve created an object-oriented container for some procedural code.
Clean Code
Clean code reads like well-written prose.
– Grady Booch
You should always remind that statement while you are coding “Write class, functions and variables with clear and explicit names as what they intend to do.”
Refactoring
When you refactor some code with your team, it’s sometimes important to pick some files from another git branch.
To do so, you can use the following git command in your working branch:
git checkout <OTHER_BRANCH_NAME> — fullpath/to/file
Manage your cognitive load
As a developer, it is also very important to manage our cognitive load. We read, review, and write code every day, and like ‘clean code’, it is really important to make our job as simple as it could be for our brain to digest it when reading our lines of code.
Not only will you be faster, but you will also increase your mood and general mental feelings.
Declaring variables in ES6+
In JavaScript ES6+, there are two main ways to declare a variable.
‘let’ and ‘const’ keywords. There is a third one, the ‘var’ keyword (that was there since the beginning of the language), however it tends to be outdated and it is wise to avoid using it anymore.
letdeclares a variable in the current scope, which can be either a function, a block or a module. Variables declared with ‘let’ won’t be visible outside of the score.
constdeclares a variable (likelet), but cannot be reassigned. In this case, it is a a constant since the value is immutable.
For your information, a value becomes immutable when it cannot be changed, which is also the case with objects.
Cloning
ES6 also offers the possibility of cloning objects and arrays.
For example:
const names = {first: “James”, last: “Bones”};
const clone = Object.assign({}, names);
Loops
Prototype.reduce()
As the name already suggest reduce method of the array object is used to reduce the array to one single value.
The reduce() is an inbuilt function in JavaScript which is used to transform an array’s /object’s properties into one single value or is used to create a single result from a given list of values.
Example:
// Declarative way to sum up the values in array
const arr = [1, 2, 3, 4, 5];
const total = arr.reduce((sum, value) => sum + value, 0);
console.log(total)
//expected output: 15
Prototype.every()
Array.every() method checks if all elements in an array pass a test (provided as a function).
The every() method executes the function once for each element present in the array:
If it finds an array element where the function returns a false value, every() returns false (and does not check the remaining values).
If no false occur, every() returns true.
Note: every() does not execute the function for array elements without values.
Note: every() does not change the original array.
Useful object features
Object.freeze()
Object.freeze() method freezes an object. A frozen object can no longer be changed; freezing an object prevents new properties from being added to it, existing properties from being removed, prevents changing the enumerability, configurability, or writability of existing properties, and prevents the values of existing properties from being changed. In addition, freezing an object also prevents its prototype from being changed. freeze() returns the same object that was passed in.
Functional Theory
Pure Function
A function is pure only if given the same input, it ALWAYS gives the same output.
A pure function never produces side-effects, meaning that it cannot change any external states. The pure function only depends on its own arguments, and from the function’s scope.
function multiply(x, y) {
return x * y;
}
A more tricky scenario can occur when you are passing an object. Imagine you are passing a “user” object to another function. If you modify the object user in the function, it will modify the actual user object because the object passed in as parameter is actually a reference of the object, which is the opposite of a distinct new cloned object.
To prevent this downside, you will have to deep clone the object first (you can use the lodash’s cloneDeep function) and then Object.freeze(copyUser) when returning it. This will guarantee the “copyUser” to be immutable.
For instance:
import { cloneDeep as _cloneDeep } from 'lodash';
function changeUser(user) {
const copyUser = _cloneDeep(user); // copyUser = { …user };
copyUser.name = ‘Edward Ford’;
return Object.freeze(copyUser);
}
A pure function makes the code easier to read, understand, and debug. You only need to focus your attention on the function itself without having to look at the surrounding environments, states and properties outside the function’s scope.
Currying
Currying is the process of transforming a function with several parameters into a series of functions each taking a single parameter.
Array.Map
The map() operator returns a new observable of the transformed values using the mapping function.
Array.Filter
The filter() operator returns a new observable of the filtered values using the predicate function.
Array.Reduce
As the name already suggest reduce method of the array object is used to reduce the array to one single value.
The reduce() is used to transform an array’s / object’s properties into one single value or is used to create a single result from a given list of values.
Object.freeze
The Object.freeze() freezes an object.
A frozen object can no longer be changed; freezing an object prevents new properties from being added to it, existing properties from being removed, prevents changing the enumerability, configurability, or writability of existing properties, and prevents the values of existing properties from being changed. In addition, freezing an object also prevents its prototype from being changed. freeze() returns the same object that was passed in.
Declare a function
There are many different ways to declare a function in JavaScript.
The simplest and oldest method is:
function my_functiin() {}
However, since ES6, you can declare a function even simpler with the so-called “fat arrow“ function which can be seen as cleaner and more elegant way of declaring a function.
const my_function = () => {}
If the function has only one single parameter, you can omit the surrounding parentheses. So it will become:
const my_function = arg => {}
Finally, you can also omit the “return” statement if the function has no conditions and doesn’t use curly brackets. In this case, it automatically returns the value.
In other words, if the function has a single expression/instruction, the arrow function has an implicit return.
Magic, isn’t it? :-)
Arrow functions
Arrow functions (also known as “fat arrow functions”) can create anonymous functions on the fly.
They can be used to create small callbacks, with a shorter syntax.
Without curly brackets {}, when the arrow function has a single expression/instruction, the arrow function has an implicit return.
const isTrue = (value) => value === true || value === ‘on’;
With the curly brackets, we need to use the ‘return’ statement.
Asynchronous code
Asynchronous is a bit like a way of doing things in JavaScript. Unless a function is returning instantaneously, we should never call it with the intention of waiting for a response.
In addition, most of the methods present in frameworks and libraries are asynchronous.
To give you an example of synchronous code, consider the following:
function wait(milliseconds) {
const startTime = Date.now();
while (Date.now() < startTime + milliseconds) {}
}
function showMessage() {
console.log('function ended.');
}
wait(2500);
showMessage();
This above code is a blocking one. ‘showMessage()’ function won’t be called until the wait() function finishes to run.
Promise
Promises was brought by ES6. They are used to handle asynchronous operations in JavaScript. They make the code more readable and easy to maintain, especially when dealing with multiple asynchronous operations where callbacks can create callback hell leading to unmanageable code.
Promises support a chaining system that allows us to pass data through a set of functions. Promises use callbacks.
Callback
Callbacks were the traditional approach to asynchronous programming in JavaScript.
Nowadays, Promises replaced callbacks for good! (it removes most of the downside we could have with callbacks).
async/await keywords
Since ES8, JavaScript supports the async and await keywords natively. This syntax makes asynchronous code more readable than callbacks and often less verbose than using Promises directly. It is now widely recommended for modern applications.
What is a hook?
A hook is the execution of a custom code (function) either before, after, or replacing existing code. For instance, a function can be written to “hook” into the login form, executing a Captcha function before proceeding with the normal login process.
Deployment: Zero Downtime
To achieve continuous zero-downtime deployments and minimize associated risks, consider implementing these strategies.
Blue-Green (A/B deploy)
The blue is the currently-running production environment receiving all traffic.
The other one (the green) is a clone of it. Both use the same database back-end and app configuration.
When deploying a new version of our application, we deploy it to the second ‘green’ environment (which is still out of traffic at the moment).
Once the new environment is fully tested internally by you and your team, and the deployment process fully completed, we can finally start switching the traffic from the blue to the green environment (where we have the new changes deployed).
The old environment can then be turned off and later on be deleted (when we are certain the new changed version works perfectly under traffic).
Canary strategy
The name of this deployment strategy may looks funny. Indeed, the name is referring from an old practice used by miners when sensor safety equipments didn’t exist yet.
A common issue with coal mines is the toxic gases, which some don’t have any smalls.
To alert themselves to the presence of dangerous gases, miners would bring cages of canaries with them into the mines. The reason is that canaries are highly susceptible to toxic gases. If the canary died, it was then time for miners to get out as fast as possible, before they’d end up like the canaries.
So, as you can now guess, canary deployment is like the blue-green deployment, except it’s less risky. Instead of switching directly from blue to green in one step, you gradually increase user traffic by load balancing it, step-by-step, while carefully monitoring it for eventual problems.
If something goes wrong, the team can roll back the release before the entire (100%) of the traffic would be affected.
Update DB structure with zero-downtime migration
If you use a relational database (such as MySQL or PostgreSQL), it can be hard to released the DB schema changes without downtime.
A solution for that can be:
Set up a new database.
Change your code to write to both the old and new database each time (and keep reading from the old database).
Back-fill the unmodified data by copying it from old to new (you can sync with DB triggers if support by your DBMS).
Update the code to start reading from the new database.
Delete the old database (later on. After a week/a month or so).
If you are using a non-relational database (such as MongoDB, ElasticSearch), you don’t have DB schemas which eliminates this kind of issues in case of changes.
Best Practices
A good habit to have is using a “deployment checklist” (you can do this on a Kanban board). For example, an item on the checklist can be to “backup all databases” to prevent data corruption.
Stateful & Stateless Architecture
Statefull Application
It means that the program uses memory concepts (state).
Example:
//The state is derived by what is passed into the function
function int addOne(int number) {
return number + 1;
}
Stateless Application
It means that there’s no memory (state) that’s maintained by the program.
Benefits of Stateless App
Scalability: Stateless systems can be scaled horizontally, and in theory there is no upper limit. The server doesn’t have to allocate and manage resources between requests.
Reliability: If a node of a stateless system fails it can be easily replaced with another node that works. There are no total failures because a failure is limited to one node and not the whole system.
Visibility: Since a stateless request is self-contained you don’t need additional information to analyze and understand its nature. It’s pretty much self explanatory and can be isolated.
Example:
// The state is maintained by the function
private int _number = 0; //initially to zero
function int addOne()
{
_number++;
return _number;
}
Best practices
When you write code, it’s not your code, it’s somebody’s else code.
Don’t show off with complex, complicated, and fancy code. Just build high-quality code.
Applying coding standards.
Analyzing code automatically.
Following coding best practices (such as the SOLID principles and clean code).
Refactoring legacy code.
Code Quality depends on everyone’s responsibility.
Documentation
Thanks to TDD, your tests can be your documentation.
TDD makes sure that everything is tested, but also can really help new devs to understand your code much easier.
Robert Martin (Uncle Bob) says that good code shouldn’t need comments in it.
Methods, functions, and class names should be explicit and short enough that you don’t need comments to understand what they do.
At the end of the day, you just need documentation for your APIs for your other team devs that write different apps using your API. You don’t want them to request each endpoint to understand what it does.
For doing so, you have fortunately, a great documentation generator for APIs like Swagger (OpenAPI).
Git commits
Each commit should be small and fix only one thing per commit, not more.
Following this good rule, if your commit message contains the word “and” or “+”, that means you try to commit multiple changes in one commit and you shouldn’t.
This rule is not just there to be nice. When you need to revert or cherry-pick a commit from your git history, the fact that one commit only contains one specific update really helps.
Commit messages
Commit messages play a big role in the understanding of every change. They should be short, clear, and explicit. They should clearly say what happened as well as the reason/motivation behind the change.
Ask yourself, “What are the changes done for?”
In other words, “Why did you do what you did?”
In short, it is the explanation that we don’t necessarily see in the code diff of the commit.
Additionally, adding the ticket number of the issue as a prefix would help in case we need further information. Sometimes, we need to know the reason why a piece of code has been changed and need to dig into the story ticket to have a full understanding of the feature requirements.
Whenever possible, mentioning the ticket number at the beginning of your commit message makes things much easier. In that way, if we need further details, we can click on the story ticket and get a better summary of what that commit does.
Algorithms
The Big ‘O’
Big O-N represents Linear Time complexity, which means the execution time increases linearly with the number of arrays in the loop.
Big(o^n) grows linearly and directly proportional to the size of the input dataset.
Merge Sort
Mergesort is a comparison-based sorting algorithm that typically produces a stable sort, meaning that the order of equal elements remains the same in the input and output.
Quick Soft
Quicksort, a widely used and straightforward sorting algorithm, was developed by Tony Hoare in 1959. When executed correctly, it can outperform ‘merge sort’ by a significant margin and is approximately two to three times faster than heapsort.
The algorithm divides the input array into two halves, recursively calls itself for each half, and then merges the two sorted halves.
Quicksort’s popularity stems from two key factors: first, it operates in-place, eliminating the need for additional memory when sorting large datasets. Second, it consistently achieves excellent average performance, making it an ideal choice for situations where the optimal sorting algorithm is uncertain.
Write better unit tests
Unit tests are so important for the rapid development of software.
However, we sometimes tend to spend too much time writing bad unit tests which are not efficient or just redundant.
We also tend to duplicate a lot of pieces of code when we add a new unit test.
Duplicating nearly the same test we already have written to end up changing only a key name or something really minor instead of creating a testing helper function for instance.
The same applies when we mock too many objects while executing our test. If too many objects are mocked, we don’t test the deep logic by calling the other related functions that interact with the tested function.
In this chapter, we will see how to write efficient, good, and DRY tests. How to automate as much as possible the tests by using some automation frameworks/libraries (like Cypress and Playwright), so we can spend less time on tests without neglecting them.
Remember, tests are there to save your day.
Unit tests
Unit tests are narrow in scope and typically verify the behaviour of individual methods or functions.
Functional tests
Writing functional tests is crucial for developing a high-quality application.
Functional testing is a software testing method that evaluates the internal structures and workings of an application, rather than its functionality. In white-box testing, an internal perspective of the system and programming skills are employed to design test cases.
Integration tests
Integration tests ensure that multiple components function correctly together. This can involve testing multiple classes and integrating them with other services.
Acceptance tests
Acceptance tests, similar to integration tests, concentrate on business cases rather than individual components.
UI tests
User interface (UI) tests ensure that the application functions correctly from the user’s perspective.
Debugging: Learn how to debug
As a developer, your primary responsibility is to find the most efficient solution to problems within your product. However, debugging is an integral part of your daily routine, as it often arises when something goes wrong.
Mastering efficiency and rapidity in coding is crucial for effective debugging.
Here are some effective debugging practices:
1. Use search engines: Start by searching for the error message in search engines like Google, Stack Overflow, or GitHub.
2. Step through the code: Go through the code line by line to identify where the program is being executed and where things are being triggered.
3. Comment code: Comment blocks of code one by one to pinpoint the flaw.
4. Use breakpoints: Utilize the breakpoints available in most IDEs to follow the flow of code execution.
5. Log your code: Log your code to identify which functions or files are being reached, helping you pinpoint the source of the problem.
6. Create additional tests: Create tests for the function(s) where the problem occurs and verify that each function’s return value is correct.
While it’s impossible to create bug-free software, you can minimize the risk of introducing new bugs.
Be consistent in your coding practices to avoid confusion and misunderstandings when others modify or edit your codebase.
Conduct thorough testing to cover all logic and use cases of your codebase. This approach reduces the likelihood of introducing new bugs and non-working code.
Debugging Actions:
Node.js Inspector: node --inspect index.js → open chrome://inspect
VS Code Debugger: Breakpoints, watch variables, call stacks.
Remote Debugging: Attach to containers (Docker) or server processes.
Error tracking: Use tools like Sentry or LogRocket to monitor runtime issues.
Code review
How do you code review someone else’s code?
When reviewing someone else’s code, the first step should be to read the ticket requirements and user story (if any) to better understand the reason behind the code changes. In the case of a feature, ensure that the code is relevant to the requested specific feature. Afterward, you should proceed with your review.
API: Best practices
Only return the needed amount of fields
Just returns the fields you need to use. Don’t return all properties from the API result.
For instance, use a GET parameter such as /users?fields=id,firstName,lastName,address,description
It makes your result cleaner and reduces the response size.
REST API endpoints are plural or singular?
We tend to write API endpoints with plural nouns (which is the preferable option) for resources.
Using plural or singular nouns for defining resources has no impact on how your API will work; however, there are common conventions that are used in all good RESTful APIs. One of such conventions is the use of plural nouns for defining resources.
Monitoring your API
It’s always a good practice to implement a /health endpoint to ensure that your API and its services (database, sub-services, and third-party ones) are responding correctly.
Don’t use verbs when naming endpoints
The verb of what your endpoints do should be the HTTP methods described as CRUD (POST, GET, PUT, PATCH, DELETE), but not the endpoint itself. The name of your endpoints should always be a noun, such as DELETE /user/:userId for deleting a user and ‘POST /user’ to create a new user.
Actions List
Use nouns, not verbs:
/users, not/getUsers.Pagination: Always paginate large responses.
Use libraries like Zod or Joi. You can also use Yup, which is less popular than the other two but remains very simple to use and effective.
Versioning: /api/v1/… to avoid breaking clients.
Monitoring: Use Prometheus + Grafana, or SaaS like Datadog.
Security: Validate Content-Type, enforce HTTPS, and add rate limiting.
Always validate the Content-Type received
The server hosting your backend API shouldn’t assume that the content type provided is the correct one.
For instance, if you accept application/x-www-form-urlencoded, an attacker can create a form and trigger a simple POST request. Therefore, always validate the content-type. If you prefer a standard content type, use the application/json. header for instance.
OpenAPI
OpenAPI specification started as the Swagger specification for documenting an API documentation.
It’s a really nice and elegant way to document efficiently your endpoints and indispensable to every professional REST APIs.
Below is an example of the way to write an OpenAPI yaml file
> openapi.yaml
openapi: 3.0.0
info:
version: 1.0.0
title: Sample API
description: A sample API documentation
paths:
/users/list:
get:
description: Returns a list of users
'200':
description: Successful response
Code Cleanup: Keep it professional
Code Linter
Linting Code is the process of running a program that will analyse your code for potential errors.
Usually, we would run it in a CI pipeline on GitHub/BitBucket.
Prettier
Prettier is a code formatter focused on enforcing consistent code style conventions to write clean and maintainable code. If you know PHP, it’s quite similar to PSR (PHP Standard Recommendation).
It is an opinionated code formatter inspired by refmt with advanced support for language features from ES2017, JSX, Flow, TypeScript, and more. It removes all original styling and ensures that all outputted code conforms to a consistent style.
Actions List
Use a linter (ESLint, StandardJS).
Use a formatter (Prettier).
Run them before commits using git hooks (husky + lint-staged).
Automate code style checks in CI.
Soft Skills: Develop your soft skills, step by step
Developing your soft skills is crucial, especially if you aspire to transition from a pure development role to something like software project management or IT management. While technical skills are essential for hiring, soft skills are key to promotion.
As a software developer, it’s important to develop your communication skills to explain complex concepts clearly. Collaboration is also vital, involving code reviews and pair programming.
Mentorship is another valuable skill, allowing you to share your knowledge and teach junior developers.
Adaptability is crucial in the fast-paced tech industry, so it’s important to stay curious and open to learning new things. Finally, empathy is essential, as you’re coding for users, not just machines.
Questions & Answers
In order to become an awesome senior developer, you should be able to know the following topics. You will find brief answers to them, and you will always be able to dig deeper if needed.
NPM Fun Fact
npm remove is just an alias of npm uninstall but does exactly the same.
The npm registry hosts over 2 million packages.
But 97% of your project’s node_modules often comes from just 20 dependencies.
Tip: Audit regularly with npm audit or pnpm audit.
Great Fonts for Your Code Editor
Space Mono (clean, geometric).
Fira Code (ligatures, very readable).
MonoLisa (paid, balanced design).
Inconsolata (classic, free).
Apercu Mono (quirky, modern).
Input Mono (customisable, developer-friendly).
Gintronic (distinctive, playful).
Operator Mono (paid, elegant italics).
⚡️ Get inspired by open-source projects I’ve built over the past years on GitHub


