Introduction
Here you will find useful information for configuring a development environment and for completing the training program.
Here you will be able to find more information about how we work and other useful information.
Feel free to ping anyone on the team if you think something can be added/improved in these guides.
Setup your machine
Code Editor
Choose your code editor
Time to configure it
-
If you chose Sublime
-
Create
subl
commandsudo mkdir -p /usr/local/bin/ && sudo ln -s /Applications/Sublime\ Text.app/Contents/SharedSupport/bin/subl /usr/local/bin/subl
-
Install package manager as shown here
-
Configure user settings (
Sublime Text -> Preferences -> Settings
){ "ensure_newline_at_eof_on_save": true, "font_size": 14, "show_encoding": true, "tab_size": 2, "translate_tabs_to_spaces": true, "trim_trailing_white_space_on_save": true }
-
-
If you chose VS Code
-
Create
code
command -
Open the Command Palette
(⇧⌘P)
and type shell command to find the Shell Command: Install 'code' command in PATH command. Restart the terminal for the new$PATH
value to take effect. You'll be able to typecode .
in any folder to start editing files in that folder. -
Configure user settings
(⇧⌘P)
and type user settings and select Preferences: Open User Settings Depending on your VSCode version you will see a JSON view of the settings by default or not. If it doesn't show at first you can click a{}
button on the top right that will show you the settings JSON view.{ "explorer.confirmDelete": false, "editor.scrollBeyondLastLine": false, "editor.wordWrap": "on", "files.trimTrailingWhitespace": true, "editor.tabSize": 2, "markdown.preview.scrollPreviewWithEditor": false, "[python]": { "editor.tabSize": 4, }, "files.insertFinalNewline": true, "markdown.preview.scrollEditorWithPreview": false, "html.suggest.html5": false, "editor.quickSuggestions": { "other": true, "comments": false, "strings": true }, "window.zoomLevel": 0, "python.venvPath": "~/.virtualenvs", } `
-
General MAC OS config
-
Enable "Show Path" bar in Finder
defaults write com.apple.finder ShowPathbar -bool true
-
Enable "Show Status" bar in Finder
defaults write com.apple.finder ShowStatusBar -bool true
Xcode
Xcode is an IDE for macOS developed by Apple for developing software for macOS, iOS, iPadOS, watchOS, and tvOS.
- Install Xcode from the App Store
- Open Xcode and accept the licence
- Install command line tools with this command:
xcode-select --install
Android Studio (optional)
Android Studio is the official IDE for Google's Android operating system, designed specifically for Android development.
Installation
You can download it from Android Studio page
Homebrew
Homebrew is a free and open-source software package management system that simplifies the installation of software on Apple's macOS operating system and Linux.
Installation
-
To install it, run in Terminal the following command:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
-
Run
brew doctor
and update homebrew withbrew update
Git
Git is a distributed version-control system for tracking changes in source code during software development. It is designed for coordinating work among programmers, but it can be used to track changes in any set of files.
Installation
Xcode developer tools already installs GIT for us.
Configuration
-
If migrating from another computer, import previous configuration following this guide
-
If no configuration was created, execute these commands in your terminal:
ssh-keygen -t rsa -C "YOUR GENERATED EMAIL" git config --global user.email "YOUR EMAIL" git config --global user.name "YOUR NAME" git config --global color.ui true
Useful tips
GitHub
GitHub is a web-based collaborative software platform for software development version control using Git. It provides access control and several collaboration features such as bug tracking, feature requests, task management, and wikis for every project.
If you are new to GitHub, create an account.
When you finished, give your account username to somebody in the team
RVM - Ruby Version Manager
RVM is a command-line tool which allows you to easily install, manage, and work with multiple ruby environments from interpreters to sets of gems.
Installation
Run the following command:
curl -sSL https://get.rvm.io | bash -s stable
source ~/.rvm/scripts/rvm
Ruby
Ruby is an interpreted, high-level, general-purpose programming language.
Installation
Run this command in Terminal:
rvm use ruby --install --default
Configuration
Configure no doc
nano ~/.gemrc
Paste this:
gem: --no-rdoc --no-ri
Install rails and bundler
gem install rails bundler
wget
GNU Wget is a computer program that retrieves content from web servers. It supports downloading via HTTP, HTTPS, and FTP.
Installation
Run the following command:
brew install wget
nvm
nvm is a version manager for node.js, designed to be installed per-user, and invoked per-shell. nvm works on any POSIX-compliant shell (sh, dash, ksh, zsh, bash), in particular on these platforms: unix, macOS, and windows WSL.
Installation
Run the following command:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash
Verification
Check that everything went well running the following command:
command -v nvm
Node.js
Node.js is an open-source, cross-platform, JavaScript runtime environment that executes JavaScript code outside of a web browser.
npm
npm (originally short for Node Package Manager) is a package manager for the JavaScript programming language. It is the default package manager for the JavaScript runtime environment Node.js.
Installation
To install both nodejs and npm, run the following command:
nvm install node
MySQL
MySQL is an open-source relational database management system. MySQL is used by many popular websites, including Facebook, Flickr, MediaWiki, Twitter, and YouTube.
Installation
Run the following command:
brew install mysql
brew services start mysql
Postgres / PostgreSQL
Postgres is a free and open-source relational database management system emphasizing extensibility and SQL compliance.
Installation
You can install it from Postgres app page
Configuration
Configure PATHs
-
Open
zshrc
:nano ~/.zshrc
-
Paste the following:
export PATH=$PATH:/usr/local/bin export PATH=$PATH:/Applications/Postgres.app/Contents/Versions/latest/bin
-
Save those changes and exit.
-
Apply those changes:
source ~/.zshrc
-
Finally, type
psql
in your terminal to open Postgres console and run:CREATE USER postgres SUPERUSER;
Python3 (optional)
Python is an interpreted, high-level, general-purpose programming language. Python3 is a version of this programming language.
Installation
Run the following command:
brew install python3
Configuration
-
Setup Python's environment management
- Install virtualenv
pip3 install virtualenv
- Install virtualenvwrapper
pip3 install virtualenvwrapper
- Use the following command to find the location of Python3 on your system
which python3
- Add the following lines to
~/.zshrc
(or your own shell's initialisation file)VIRTUALENVWRAPPER_PYTHON='<Python3 location>' source /usr/local/bin/virtualenvwrapper.sh export WORKON_HOME=$HOME/.virtualenvs
- Run the following commands
mkdir ~/.virtualenvs source ~/.zshrc
- All the virtual environments created using virtualenvwrapper will now be stored in
~/.virtualenvs
- Install virtualenv
-
To create new Python3 virtual environment
mkvirtualenv <project name>
The virtualenv will automatically activate after creation
-
To exit the virtualenv
deactivate
-
To access the virtualenv
workon <project name>
Troubleshooting
If you experienced errors while bundling, run this commands in Terminal:
gem uninstall libv8
brew install v8
gem install therubyracer
gem install libv8 -v '3.16.14.3' -- --with-system-v8
Extra (optional)
libxslt
libxslt is the XSLT C library developed for the GNOME project. It is used for XML parsing, tree manipulation and XPath support.
Installation
To install it, run in the Terminal:
brew install libxslt
Docker (optional)
Docker is a set of platform as a service products that uses OS-level virtualization to deliver software in packages called containers.
Installation
You can install Docker Desktop app from docker page.
Training program
This training program will help new developers learn all the basic concepts and technologies that we use on a daily basis. We are going to start learning GIT. Then, we are going to learn HTML, then a little of CSS and JS. After that, we will start learning how to implement a web server. We will also learn how to use databases and finally we will dive into Ruby on Rails (RoR) or Node.js.
We will start a small project and improve it at each step.
Please feel free to make any recommendations to improve it.
Common section
This section contains all the common parts of the trainging program.
The Unix Shell
There are multiple ways of interacting with a computer. In general, operating systems use either a graphical user interface (GUI) with a mouse, or a command-line interface (CLI) with just a keyboard.
The shell is just a program that exposes the operating system's services to a person or other programs via a CLI. As you might imagine, there are multiple implementations of the shell program. The two most common nowadays are bash
and zsh
(Linux usually uses bash
while MacOS uses zsh
, which is pretty similar to bash
).
Think of the shell as both a command line interface and a scripting language, allowing you to automate repetitive tasks if needed.
The shell is one of the most powerful tools a developer can have. Once you master it, it will allow you to perform mundane task (such as viewing the contents of a directory) and really complex task, such as copying files to a remote computer in another part of the world.
Basics
If you don't have experience with the shell, we recommend you to go through this tutorial first.
If you have some experience, please check this link and make sure you are familiar with most of the concepts presented there.
Resources
Git
Git is a powerful tool used by developers all around the world to control and manage changes to a project over time.
As a version control software, it keeps track of every modification to the code in a special kind of database. If a mistake is made, developers can turn back the clock and compare earlier versions of the code to help fix the mistake while minimizing disruption to all team members.
Version control also helps teams solve compatibility problems. Changes made in one part of the software can be incompatible with those made by another developer working at the same time and this problem should be discovered and solved in an orderly manner without blocking the work of the rest of the team.
Luckily, the possibility of tracking every individual change by each contributor discourages conflicting concurrent work and, if it happens to be the case, git also offers a great variety of commands to solve this issue in a ordered way to make sure no relevant work is lost.
Please read Chapter 1.1 through 1.4 of this link. Be sure to understand The Three States
section in Chapter 1.3.
Complete the tutorial
Here you got a interactive webpage that teaches you Git basic commands using visual tree representation: Learn Git Branching
You will see the page has two tabs: Main and Remote. Each tab also has sections and levels with problems to solve.
To continue, please complete the following levels:
-
Main tab
- Introduction Sequence
- Ramping Up
- Moving Work Around
- A Mixed Bag - levels 1 & 2
- Advanced Topics - only first level (Rebasing)
-
Remote tab
- Push & Pull -- Git Remotes!
- To Origin And Beyond -- Advanced Git Remotes! - levels 1, 2 & 4
Pay special attention to the rebase
command and everything that relates to it (specially the advanced topic, first level). We will use it on a daily basis.
After completing all the levels previously mentioned, we highly recommend you to read the following article about writing meaningful commit messages: How to Write Better Git Commit Messages
Learn HTML
HTML is the standard markup language for creating Web pages.
- HTML stands for Hyper Text Markup Language
- HTML describes the structure of Web pages using markup
- HTML elements are the building blocks of HTML pages
- HTML elements are represented by tags
- HTML tags label pieces of content such as "heading", "paragraph", "table", and so on
- Browsers do not display the HTML tags, but use them to render the content of the page
An HTML page is just a plain text file with .html
extension that you can edit on any text editor.
Please be careful and use the correct tag for each case. If you want a paragraph use <p>
, if you need to add a table use <table>
, for every component that you want to add to the UI, search for the correct tag. Here you can see a list of all html tags.
We use the following style guide for HTML code, except for the indentation part where we use this syntax:
<html>
<head></head>
<body>
<h1>Hello</h1>
<div>...</div>
</body>
</html>
Exercise
In the next 3 sections (HTML, CSS and JS), we are going to build a tool to create and control monthly tasks. The user can mark tasks as completed and the platform will send the user a reminder if the task hasn't been completed before end of month. At the beginning of each month the tasks will automatically get back to uncompleted.
We will start using GIT from now on, so please create a folder inside your repository each time you start a new section. You should end up with a folder structure similar to this:
html/excercise1.html
...
html/excercise3.html
css/plain_css
...
css/bootstrap
...
For this section, please build the following 3 web pages using only HTML (without CSS or Javascript). In order to check that the HTML you generated is valid, you can use this validator.



Important Note
When you create the first Pull Request for this repo, you will see that some strange files will appear in your git stage (for example .DS_Store
files). These files should not be commited and can be ignored by having a .gitignore
file that basically ignores whatever you tell git to ignore. You can take a look at this repo to see specific gitignore files for every language/technology.
For this section you could use a general macOS gitignore.
Learn CSS
What is CSS?
CSS is the language we use to style a Web page.
- CSS stands for Cascading Style Sheets
- CSS describes how HTML elements are to be displayed on screen, paper, or in other media
- CSS saves a lot of work. It can control the layout of multiple web pages all at once
- External stylesheets are stored in CSS files
Feel free to check this CSS guide that goes more in depth at any time.
Syntax
CSS consist of a set of rules that are interpreted by the browser and are applied to corresponding elements in the web page. Each rule is made of three parts:
-
Selector: An HTML tag at which a style will be applied. This could be any tag like
<h1>
,<table>
, etc. -
Property: Type of attribute of HTML tag. They could be color, border etc.
-
Value: Values are assigned to properties. For example, color property can have value either
red
,#F1F1F1
, etc.
Rule:
selector {
property: value;
}
You can have multiple types of selectors, below is a quick list.
Universal selector
Matches any element type.
* {
color: #000000;
}
So in this case every element is going to be black.
Type selector
Selects an element of a specific type: h1
, p
, table
, etc.
h2 {
color: #0000FF;
}
Descendant selector
ul em {
color: #000000;
}
Applies the color to <em>
elements only inside a <ul>
tag.
Class selector
You can define style rules based on the class
attribute of the elements. All the elements having that class will be formatted according to the defined rule.
.black {
color: #000000;
}
This rule renders the content black for every element with class attribute set to black
in our document.
You can even make it more particular, only rendering the content in black for <h1>
elements with a black
class.
h1.black {
color: #000000;
}
You can then use this class and apply it to an HTML element like this:
<p class="black center">
This paragraph will be styled by the classes black and center.
</p>
Id selector
You can define rules based on the id
attribute of the elements. All the elements having that id
will be formatted according to the defined rule. You should not have more than one element with the same id
in the same page.
#login-button {
color: #FF0000;
}
The true power of id
selectors is when they are used as the foundation for descendant selectors. For example:
#login-form h2 {
color: #0000FF;
}
Lets us only apply the blue color to the h2
if it's a descendant of the login form (which should be unique).
Child selector
This rule is very similar to the descendant selector, but applies only to direct childs.
body > p {
color: #000000;
}
Will render all the paragraphs in black if they are direct child of <body>
element.
Attribute Selectors
You can apply styles to to HTML elements with particular attributes like this:
input[type = "text"] {
color: #000000;
}
This will match all input
elements with type of text
(and not submit
for example).
Grouping selectors
You can apply a style to many selectors just by separating the selectors by a comma like this:
h1, h2, h3 {
color: #36C;
font-weight: normal;
text-transform: lowercase;
}
Let's practice
In order to practice CSS selectors, let's complete this game here.
Layout
When you use CSS to create a layout, you are moving the elements away from the normal flow. The methods that can change how elements are laid out in CSS are as follows:
- The display property — Standard values such as block, inline or inline-block can change how elements behave in normal flow.
- Floats — Applying a float value such as left can cause block level elements to wrap alongside one side of an element, like the way images sometimes have text floating around them in magazine layouts.
- The position property — Allows you to precisely control the placement of boxes inside other boxes. Static positioning is the default in normal flow, but you can cause elements to be laid out differently using other values, for example always fixed to the top left of the browser viewport.
- The Flexbox Layout - aims at providing a more efficient way to lay out, align and distribute space among items in a container, even when their size is unknown and/or dynamic.
The display property
The main methods of achieving page layout in CSS are all values of the display property.
For example, the reason that paragraphs in English display one below the other is due to the fact that they are styled with display: block
. If an HTML element has display: block
, it tries to fill all the available space horizontally.
If you create a link around some text inside a paragraph, that link remains inline with the rest of the text, and doesn’t break onto a new line. This is because the <a>
element is display: inline
by default. display: inline
sets the height and width of an element to the minimum possible and you can't change the width or height properties of the HTML element. If you want to have the same properties as display: inline
but also have the possibility to change width or height, you must use display: inline-block
.
Finally display: none
removes the element from the layout. The display
property has more available options but these are the most commonly used.
Floats
Floating an element changes the behavior of that element and the block level elements that follow it in normal flow. The element is moved to the left or right and removed from normal flow, and the surrounding content floats around the floated item.
The float property has four possible values:
- left — Floats the element to the left.
- right — Floats the element to the right.
- none — Specifies no floating at all. This is the default value.
- inherit — Specifies that the value of the float property should be inherited from the element's parent element.
<h1>Simple float example</h1>
<div class="box">Float</div>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla luctus aliquam dolor, eu lacinia
lorem placerat vulputate. Duis felis orci, pulvinar id metus ut, rutrum luctus orci. Cras
porttitor imperdiet nunc, at ultricies tellus laoreet sit amet. Sed auctor cursus massa at porta.
Integer ligula ipsum, tristique sit amet orci vel, viverra egestas ligula. Curabitur vehicula
tellus neque, ac ornare ex malesuada et. In vitae convallis lacus. Aliquam erat volutpat.
Suspendisse ac imperdiet turpis. Aenean finibus sollicitudin eros pharetra congue. Duis ornare
egestas augue ut luctus. Proin blandit quam nec lacus varius commodo et a urna. Ut id ornare
felis, eget fermentum sapien.
</p>
.box {
float: left;
width: 150px;
height: 150px;
margin-right: 30px;
}

More information about the float property here.
Positioning techniques
Positioning allows you to move an element from where it would be placed when in normal flow to another location. Positioning isn’t a method for creating your main page layouts, it is more about managing and fine-tuning the position of specific items on the page.
There are however useful techniques for certain layout patterns that rely on the position property. Understanding positioning also helps in understanding normal flow, and what it is to move an item out of normal flow.
There are five types of positioning you should know about:
- Static positioning is the default that every element gets.
- Relative positioning allows you to modify an element's position on the page, moving it relative to its position in normal flow — including making it overlap other elements on the page.
- Absolute positioning moves an element completely out of the page's normal layout flow, like it is sitting on its own separate layer. From there, you can fix it in a position relative to the edges of its nearest positioned ancestor element (element without static position).
- Fixed positioning is very similar to absolute positioning, except that it fixes an element relative to the browser viewport.
Flexbox Layout
The main idea behind the flex layout is to give the container the ability to alter its items’ width/height (and order) to best fill the available space (mostly to accommodate to all kind of display devices and screen sizes). A flex container expands items to fill available free space or shrinks them to prevent overflow.
Flex container
To use flexbox you need to define a flex container. A flex container enables a flex context for all its direct children.
.container {
display: flex; /* or inline-flex */
}
Flex orientation
You can define the direction flex items are placed in the flex container. Think of flex items as primarily laying out either in horizontal rows or vertical columns. By default flex uses horizontal orientation.
.container {
flex-direction: row | row-reverse | column | column-reverse;
}
Justify content
This defines the alignment along the main axis. It helps distribute extra free space leftover when either all the flex items on a line are inflexible, or are flexible but have reached their maximum size.
.container {
justify-content: flex-start | flex-end | center | space-between | space-around | space-evenly |
start | end | left | right... + safe | unsafe;
}
Align Items
This defines the default behavior for how flex items are laid out along the cross axis on the current line. Think of it as the justify-content version for the cross axis (perpendicular to the main-axis).
.container {
align-items: stretch | flex-start | flex-end | center | baseline | first baseline | last baseline
| start | end | self-start | self-end +... safe | unsafe;
}
Flex items
Flex grow
This defines the ability for a flex item to grow if necessary. It accepts a unit-less value that serves as a proportion. It dictates what amount of the available space inside the flex container the item should take up.
If all items have flex-grow set to 1, the remaining space in the container will be distributed equally to all children. If one of the children has a value of 2, the space in the container would take up twice as much space as the others (or it will try to, at least).
Let's practice flexbox
In order to practice flexbox, let's complete this game here.
Coding styles
There are multiple ways in which CSS code can be formatted. Since having as many coding styles as developers would be catastrophic, we have a way of formatting/styling CSS code.
At eagerworks we use Google's style guide. Take some time to take a look at it.
Exercise #1
Part A
To catch and address any issues early on and prevent them from compounding, please implement only the first two sections of the proposed UI. In this excercise we will use plain CSS (you will define all the styles/classes).
You can check colors, fonts and assets on Figma. Ask your mentor for some guidance into how to check font sizes, colors, etc.
Part B
Reimplement the previous part, but now using utility classes.
Part C
Add SASS to the previous part. You can use this command to recompile the CSS on every change:
sass --watch input.scss output.css
Once you have that in place, go ahead and implement the full UI.
Part D
Now it's time to make sure that the UI is responsive and looks good on any screen size. You will need to add media queries to your CSS.
Exercise #2
Tailwind is a CSS framework that makes web implementation a lot easier. It provides numerous predefined classes and utilities, allowing us to implement any UI quickly and in a maintainable way.
Please take some time to read a Tailwind's documentation (taking special care to the breakpoints section) and then reimplement the same UI as you did on the previous exercise, but now using Tailwind. If you need to override something, try customizing the theme.
Learn JS
JavaScript ("JS" for short) is a full-fledged dynamic programming language that, when applied to an HTML document, can provide dynamic interactivity on websites.
On Google Chrome you can access a JS console by doing Right-click -> Inspect -> Console
Callbacks
A callback is a function that is to be executed after another function has finished executing — hence the name ‘call back’. JavaScript is an event driven language. This means that instead of waiting for a response before moving on, JavaScript will keep executing while listening for other events.
What if function contains some sort of code that can’t be executed immediately? For example, an API request where we have to send the request then wait for a response? To simulate this action, were going to use setTimeout
which is a JavaScript function that calls a function after a set amount of time. We’ll delay our function for 500 milliseconds to simulate an API request. Our code will look like this:
function first() {
// Simulate a code delay
setTimeout(function() {
console.log(1);
}, 500);
}
function second() {
console.log(2);
}
first();
second();
So what happens now when we invoke our functions?
first();
second();
// 2
// 1
Selectors
To be able to change the UI and react to user events, we need to select HTML elements and modify them. For example we can get the title of a site by doing:
var myHeading = document.querySelector('h1');
querySelector
uses CSS selectors and is the most versatile of the JS selectors. JS also provides getElementById
, getElementsByClassName
and others.
Events
Real interactivity on a website needs events. These are code structures which listen for things happening in the browser, running code in response. The most obvious example is the click
event, which is fired by the browser when you click on something with your mouse.
document.querySelector('html').addEventListener('click', function() {
alert('Ouch! Stop poking me!');
})
Exercise 1
We will implement a wishlist. Please implement the UI showed in the images below. The user is able to see a list of all the items she/he wants to buy in the future. By pressing the "ADD" button the user will have a form to add new items to the list.
The user can also optimize which items to buy with a limited budget. When the user presses the "OPTIMIZE" button, the whishlist should be sorted from lowest cost to largest and the greatest number of items should be checked based on the budget.
Restriction: You can only use up to two iterations to optimize the wishlist.
Please use only plain JS (no libraries) in this exercise.


Exercise 2
We don't want to lose all items when the page is refreshed. To prevent this, when the user creates a new item, we need to persist it using localStorage
. Then, we will populate the wishlist with the previously stored items.
Additional Resources
- How to correctly name variables
- Things you SHOULDN'T do
- If you want to go deep into Javascript, check this javascript tutorial
Learn Web Architecture

The above diagram is a representation of a simple web architecture. Next we will walk you through each component, providing an introduction to each one that should give you a good mental model for thinking through web architecture going forward.
The web follows a client/server architecture in which the server hosts, delivers and manages most of the resources and services to be consumed by the client.
The main thing to understand in a web application, is that there are basically two programs running at the same time:
- The code that lives on the server and responds to HTTP requests.
- The code that lives in the browser and responds to user input.
Client Side
The browser is a program that understands HTML, CSS, and Javascript which can also make HTTP requests. It uses those HTTP request to communicate to the Web Server. Each time the user enters a url or clicks a link the browser fires a request to the server. The web server will respond with an HTML file that the browser is capable of understanding. The client side ONLY communicates with the server with requests, it has no access to the server's code.
DNS
DNS stands for “Domain Name System” and it’s a backbone technology that makes the world wide web possible. At the most basic level, DNS provides a key/value lookup from a domain name (e.g., google.com) to an IP address (e.g., 85.129.83.120), which is required in order for your computer to route a request to the appropriate server. Analogizing to phone numbers, the difference between a domain name and IP address is the difference between “call John Doe” and “call 201-867–5309.” Just like you needed a phone book to look up John's number in the old days, you need DNS to look up the IP address for a domain. So you can think of DNS as the phone book for the internet.
Web Application Server
On the hardware side, a web server is a computer that stores web server software and a website's component files (e.g. HTML documents, images, CSS stylesheets, and JavaScript files). It is connected to the Internet and supports physical data interchange with other devices connected to the web. On the software side, a web server executes the core business logic that handles a user's request and sends back HTML to the user's browser.
Database Server
Every modern web application leverages one or more databases to store information. Databases provide ways of defining your data structures, inserting new data, finding existing data, updating or deleting existing data, performing computations across the data, and more.
REST/CRUD
What is an API?
In the context of a web platform, an API (Application Programming Interface) is an interface between the different parts of the platform. It is the way the different parts of the system communicate with each other. For example the backend could expose an API that the frontend consumes in order to retrieve information to display in a web page.
HTTP Verbs
The HTTP protocol defines methods (usually called verbs) to indicate the desired action to be performed on a specific resource. The most commonly used HTTP verbs are:
- GET: requests a representation of the specified resource. Requests using GET should only retrieve data and should have no other effect.
- POST: requests that a web server accepts the data enclosed in the body of the request message, most likely for storing it. It is often used when uploading a file or when submitting a completed web form.
- PUT: replaces the resource at the current URL with the resource contained within the request. PUT is used to both create and update the state of a resource on the server
- PATCH: applies partial modifications to a resource. It is mostly used to update a resource.
- DELETE: deletes the specified resource
- OPTIONS: returns the HTTP methods that the server supports for the specified URL. This can be used to check the functionality of a web server
- HEAD: asks for a response identical to that of a GET request, but without the response body. This is useful for retrieving meta-information written in response headers, without having to transport the entire content.
REST
REST (REpresentational State Transfer) is about constraining the way we interact between client and server, to take advantage of what the protocol (in this case, HTTP) offers. These constraints give us freedom to focus on our API design:
- Uniform interface: requests from different clients look the same, whether the client is a browser, mobile device, or anything else.
- Client-server separation: the client and the server act independently and the interaction between them is only in the form of requests and responses.
- Stateless: the server does not remember anything about the user who uses the API, so all necessary information to process the request must be provided by the client on each request. Note: this isn't about storing server-side state.
- Layered system: the client is agnostic as to how many layers, if any, there are between the client and the actual server responding to the request. This is a key principle of HTTP, allowing for caching servers, reverse proxies, and access security layering – all transparent to the client sending the request.
- Cacheable: the server response must contain information about whether or not the data is cacheable, allowing the client and/or intermediaries (see layered constraint, above) to cache data outside of the API server.
- Code-on-demand (optional): the client can request code from the server, usually in the form of a script, for client-side execution.
CRUD
CRUD stands for Create, Read, Update and Delete. In the context of a RESTful API, we want them to have a standarized use of HTTP verbs and each action on our API will map to a specific CRUD action in a database.
Example RESTful API endpoints
If our system handles photos and we want to have an API to expose them, this could be a list of endpoints we will have in our RESTful API:
HTTP Verb | Path | Used for |
---|---|---|
GET | /photos | display a list of all photos |
GET | /photos/new | return an HTML form for creating a new photo |
POST | /photos | create a new photo |
GET | /photos/:id | display a specific photo |
GET | /photos/:id/edit | return an HTML form for editing a photo |
PATCH/PUT | /photos/:id | update a specific photo |
DELETE | /photos/:id | delete a specific photo |
Learn Databases
A database is an organized collection of data, generally stored and accessed electronically from a computer system.
- Create a database
CREATE DATABASE learn_to_code;
\c learn_to_code
- Create a table
CREATE TABLE users(
id serial PRIMARY KEY,
username VARCHAR (50) NOT NULL
);
- Insert data into tables
INSERT INTO users (username) VALUES('alex')"
- Get information from the database
SELECT * FROM users WHERE id=1;
Exercise
Let's create the reality of the previous exercises on a relational database. Our reality has users that want to choose favourite items. Items can be marked as bought and can be shared with other users. Users can follow other users to check the state of their favourites items. Users can also add comments to other users' favourite items.
First, we need to create an ERD diagram to represent this reality. This diagram will allow us to have a clear idea of the database structure. Then we will create this database on Postgres.
Finally, please create these four SQL queries:
- Fetch all the favourite items for a given user
- Fetch all users together with their favourite items, even if the user doesn't have any items
- Get the number of users without favourite items
- Fetch the name of the users that have more than five followers and some non bought favourite items
TypeScript
JavaScript, one of the world's most-used programming languages, has become the official language of the web. Developers use it to write cross-platform applications that can run on any platform and in any browser.
Although JavaScript is used to create cross-platform apps, it wasn't conceived for large apps involving thousands or even millions of lines of code. JavaScript lacks the features of more mature languages that power today's sophisticated applications. Integrated development editors (IDEs) can find it challenging to manage JavaScript and maintain these large code bases.
TypeScript addresses the limitations of JavaScript, doing so without compromising the critical value proposition of JavaScript: the ability to run your code anywhere and on every platform, browser, or host.
TypeScript is an open-source language that was developed by Microsoft. It is a superset of JavaScript, which means you can continue using the JavaScript skills you've already acquired and add specific features previously unavailable to you.
Typescript works by adding types on top of JavaScript. Types are a way to describe the shape of an object, providing better documentation and allowing TypeScript to validate that your code is working correctly.
It does this by providing extra syntax, which is compiled into JavaScript.
Why TypeScript?
The core feature of TypeScript is its type system. In TypeScript, you can identify the data type of a variable or parameter by using a type hint.
Through static type checking, TypeScript catches code issues early in development that JavaScript can't usually catch until the code is run in the javascript engine. Types also let you describe what your code is intended to do. If you're working on a team, a teammate who comes in after you can easily understand it too.
Types also power the intelligence and productivity benefits of development tools like IntelliSense. This is significantly potentiated by using inference.
Inference is the process of determining the type of a variable based on its usage. For example, if you declare a variable and initialize it with a string, TypeScript will infer that it is a string. So most of the time, you will write almost JavaScript code, but with the added benefit of type checking.
Gotchas
It is important to note that we never run TypeScript. We are always running JS code. TypeScript is compiled (or transpiled) into JS code since browsers (and node) do not understand TypeScript.
This means that types are not checked at runtime, only at compile time. So we must be careful with unexpected cases like APIs or user input where we can't be sure of the type.
Examples
These are some small examples of typescript
. We recommend you to try them in the Typescript Playground.
Basic Types
// Boolean
let isDone: boolean = false;
// In this case, we can let typescript infer the type from the assingment. The following syntax is equivalent:
let isDoneInferred = false;
// Declaring a type
type SomeType = {
example: string;
};
let example: SomeType = { example: 'example' };
// This will produce a compile error
let badExample: SomeType = { example: 123 };
// Interface are types that can be extended
interface SomeInterface {
example: string;
}
interface SomeInterface2 extends SomeInterface {
example2: string;
}
let example2: SomeInterface2 = { example: 'example', example2: 'example2' };
// Function
function add(a: number, b: number): number {
return a + b;
}
// The return type is optional since it can be inferred
function addInferred(a: number, b: number) {
return a + b;
}
// Arrow function
const addArrow = (a: number, b: number): number => {
return a + b;
};
// The return type is optional since it can be inferred
const addArrowInferred = (a: number, b: number) => {
return a + b;
};
Examples in React
interface Props {
name: string;
}
// Here the return type is inferred to be a ReactElement
const Example = ({ name }: Props) => {
return <div>{name}</div>;
};
// Same here
function Example({ name }: Props) {
return <div>{name}</div>;
}
// If we want it to be explicit
const Example: React.FC<Props> = ({ name }) => {
return <div>{name}</div>;
};
// or
function Example({ name }: Props): ReactElement {
return <div>{name}</div>;
}
Resources
There are many more details of typescript. The ones specified above are only the basics. You can find more information in the following resources:
- Typescript in 100 seconds
- Typescript - The Basics
- Microsoft Typescript Course
- Beginner's TypeScript by Matt Pocock
- Typescript Tips by Matt Pocock
- Typescript: The Big Picture
- TypeScript: Getting Started
Ruby on Rails
In this section you are going to learn about the Ruby on Rails framework.
Ruby
Inspired from ruby quickstart.
Ruby comes with a program that will show the results of any Ruby statements you feed it. Playing with Ruby code in interactive sessions like this is an excellent way to learn the language. If you’re using macOS open up Terminal
and type irb
(Interactive Ruby), then hit enter.
Type this:
irb(main):002:0> puts "Hello World"
Hello World
=> nil
puts
is the basic command to print something out in Ruby. But then what’s the => nil
bit? That’s the result of the expression. puts
always returns nil
, which is Ruby’s absolutely-positively-nothing value.
Methods
What if we want to say Hello
a lot without getting our fingers all tired? We need to define a method!
def hi
puts "Hello World!"
end
The code def hi
starts the definition of the method. It tells Ruby that we’re defining a method, that its name is hi
. The next line is the body of the method, the same line we saw earlier: puts "Hello World"
. Finally, the last line end tells Ruby we’re done defining the method.
irb(main):013:0> hi
Hello World!
=> nil
irb(main):014:0> hi()
Hello World!
=> nil
Well, that was easy. Calling a method in Ruby is as easy as just mentioning its name to Ruby. If the method doesn’t take parameters that’s all you need. You can add empty parentheses if you’d like, but they’re not needed.
What if we want to say hello to one person, and not the whole world? Just redefine hi
to take a name as a parameter.
def hi(name)
puts "Hello #{name}!"
end
irb(main):018:0> hi("Matz")
Hello Matz!
What’s the #{name}
bit? That’s Ruby’s way of inserting something into a string. The bit between the braces is turned into a string (if it isn’t one already) and then substituted into the outer string at that point.
Object oriented programming
If you haven't seen object oriented programming before, we recommend you to check this before continuing. If you still want to know more about object oriented programming in Ruby, we recommend checking this course.
Classes
What if we want a real greeter around, one that remembers your name and welcomes you and treats you always with respect. You might want to use an object for that. Let’s create a Greeter
class.
class Greeter
def initialize(name = "World")
@name = name
end
def say_hi
puts "Hi #{@name}!"
end
def say_bye
puts "Bye #{@name}, come back soon."
end
end
The new keyword here is class.
This defines a new class called Greeter and a bunch of methods for that class. Also notice @name
. This is an instance variable, and is available to all the methods of the class. As you can see it's used by say_hi
and say_bye
.
Now let’s create a greeter object and use it:
irb(main):035:0> greeter = Greeter.new("Pat")
=> #<Greeter:0x16cac @name="Pat">
irb(main):036:0> greeter.say_hi
Hi Pat!
=> nil
irb(main):037:0> greeter.say_bye
Bye Pat, come back soon.
=> nil
Once the greeter object is created, it remembers that the name is Pat. Hmm, what if we want to get at the name directly?
irb(main):038:0> greeter.@name
SyntaxError: (irb):38: syntax error, unexpected tIVAR, expecting '('
Nope, can’t do it.
Instance variables are hidden away inside the object. They’re not terribly hidden, you see them whenever you inspect the object, and there are other ways of accessing them, but Ruby uses the good object-oriented approach of keeping data sort-of hidden away.
So what methods do exist for Greeter objects?
irb(main):039:0> Greeter.instance_methods
=> [:say_hi, :say_bye, :instance_of?, :public_send,
:instance_variable_get, :instance_variable_set,
:instance_variable_defined?, :remove_instance_variable,
:private_methods, :kind_of?, :instance_variables, :tap,
:is_a?, :extend, :define_singleton_method, :to_enum,
:enum_for, :<=>, :===, :=~, :!~, :eql?, :respond_to?,
:freeze, :inspect, :display, :send, :object_id, :to_s,
:method, :public_method, :singleton_method, :nil?, :hash,
:class, :singleton_class, :clone, :dup, :itself, :taint,
:tainted?, :untaint, :untrust, :trust, :untrusted?, :methods,
:protected_methods, :frozen?, :public_methods, :singleton_methods,
:!, :==, :!=, :__send__, :equal?, :instance_eval, :instance_exec, :__id__]
Whoa. That’s a lot of methods. We only defined two methods. What’s going on here? Well this is all of the methods for Greeter objects, a complete list, including ones defined by ancestor classes. If we want to just list methods defined for Greeter we can tell it to not include ancestors by passing it the parameter false, meaning we don’t want methods defined by ancestors.
irb(main):040:0> Greeter.instance_methods(false)
=> [:say_hi, :say_bye]
But what if you want to be able to view or change the name? Ruby provides an easy way of providing access to an object’s variables.
irb(main):044:0> class Greeter
irb(main):045:1> attr_accessor :name
irb(main):046:1> end
=> nil
In Ruby, you can open a class up again and modify it. The changes will be present in any new objects you create and even available in existing objects of that class. So, let’s create a new object and play with its @name
property.
irb(main):047:0> greeter = Greeter.new("Andy")
=> #<Greeter:0x3c9b0 @name="Andy">
irb(main):048:0> greeter.respond_to?("name")
=> true
irb(main):049:0> greeter.respond_to?("name=")
=> true
irb(main):050:0> greeter.say_hi
Hi Andy!
=> nil
irb(main):051:0> greeter.name="Betty"
=> "Betty"
irb(main):052:0> greeter
=> #<Greeter:0x3c9b0 @name="Betty">
irb(main):053:0> greeter.name
=> "Betty"
irb(main):054:0> greeter.say_hi
Hi Betty!
=> nil
Using attr_accessor defined two new methods for us, name
to get the value, and name=
to set it.
Flow control
This greeter isn't all that interesting though, it can only deal with one person at a time. What if we had some kind of MegaGreeter that could either greet the world, one person, or a whole list of people?
Let’s write this one in a file instead of directly in the interactive Ruby interpreter IRB.
#!/usr/bin/env ruby
class MegaGreeter
attr_accessor :names
# Create the object
def initialize(names = "World")
@names = names
end
# Say hi to everybody
def say_hi
if @names.nil?
puts "..."
elsif @names.respond_to?("each")
# @names is a list of some kind, iterate!
@names.each do |name|
puts "Hello #{name}!"
end
else
puts "Hello #{@names}!"
end
end
# Say bye to everybody
def say_bye
if @names.nil?
puts "..."
elsif @names.respond_to?("join")
# Join the list elements with commas
puts "Goodbye #{@names.join(", ")}. Come back soon!"
else
puts "Goodbye #{@names}. Come back soon!"
end
end
end
mg = MegaGreeter.new
mg.say_hi
mg.say_bye
# Change name to be "Zeke"
mg.names = "Zeke"
mg.say_hi
mg.say_bye
# Change the name to an array of names
mg.names = ["Albert", "Brenda", "Charles",
"Dave", "Engelbert"]
mg.say_hi
mg.say_bye
# Change to nil
mg.names = nil
mg.say_hi
mg.say_bye
Save this file as mega_greeter.rb
, and run it as ruby mega_greeter.rb
. The output should be:
Hello World!
Goodbye World. Come back soon!
Hello Zeke!
Goodbye Zeke. Come back soon!
Hello Albert!
Hello Brenda!
Hello Charles!
Hello Dave!
Hello Engelbert!
Goodbye Albert, Brenda, Charles, Dave, Engelbert. Come
back soon!
...
...
So, looking deeper at our new program, notice the initial lines, which begin with a hash mark (#). In Ruby, anything on a line after a hash mark is a comment and is ignored by the interpreter. The first line of the file is a special case, and under a Unix-like operating system (Linux, macOS, etc.) tells the shell how to run the file. The rest of the comments are there just for clarity.
Our say_hi
method has become a bit trickier:
# Say hi to everybody
def say_hi
if @names.nil?
puts "..."
elsif @names.respond_to?("each")
# @names is a list of some kind, iterate!
@names.each do |name|
puts "Hello #{name}!"
end
else
puts "Hello #{@names}!"
end
end
It now looks at the @names
instance variable to make decisions. If it's nil
, it just prints out three dots. No point greeting nobody, right?
Cycling and Looping
If the @names
object responds to each
, it is something that you can iterate over, so iterate over it and greet each person in turn. Finally, if @names
is anything else, just let it get turned into a string automatically and do the default greeting.
Let's look at that iterator in more depth:
@names.each do |name|
puts "Hello #{name}!"
end
each
is a method that accepts a block of code then runs that block of code for every element in a list, and the bit between do
and end
is just such a block. A block is like an anonymous function. The variable between pipe characters (|
) is the parameter for this block.
What happens here is that for every entry in a list, name
is bound to that list element, and then the expression puts "Hello #{name}!"
is run with that name.
Most other programming languages handle going over a list using the for loop, which in C looks something like:
for (i = 0; i < number_of_elements; i++)
{
do_something_with(element[i]);
}
This works, but isn’t very elegant. You need a throw-away variable like i
, have to figure out how long the list is, and have to explain how to walk over the list. The Ruby way is much more elegant, all the housekeeping details are hidden within the each
method, all you need to do is to tell it what to do with each element. Internally, the each method will essentially call yield "Albert"
, then yield "Brenda"
and then yield "Charles"
, and so on.
Ruby Style Guide
Writing high quality code is essential to have easy to read and easy to maintain products. Please check the ruby styleguide.
Additional resources
- The odin project ruby course
Micro web server
A Web server is a program that uses HTTP (Hypertext Transfer Protocol) to serve web pages to users. In this step we are going to use a microframework to build a simple web server to serve our wishlist platform. A microframework is a term used to refer to minimalistic web application frameworks. It is contrasted with full-stack frameworks.
Sinatra
Sinatra is a DSL (Domain Specific Language) for quickly creating web applications in Ruby with minimal effort.
Create a server.rb
file with the following content:
require 'sinatra'
get '/' do
'Hello world!'
end
In order to use the sinatra library, you will first need to install it:
gem install sinatra
A gem is a module/library that you can install and use in every project that you need.
Now you can run your server by doing:
ruby server.rb
If you navigate to localhost:4567
on your browser you will see the response from the server to your request.
Let’s add an HTML view. Put the following code into an index.erb
file inside the views
directory (you will need to create it):
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Hello World!</title>
</head>
<body>
<h1><%= @message %></h1>
</body>
</html>
change your endpoint code to:
get '/' do
@title = 'Hello World!'
erb :index
end
All instance variables will be available in the view for you to use. We are using the erb
extension on the view file. ERB (Embedded RuBy) is a feature of Ruby that enables you to use Ruby to generate text, in our case HTML text.
Exercise 1
Let's use Sinatra to serve the wishlist items platform we created in the Javascript section. Convert all your .html
files to .erb
and create endpoints to return these views and handle all existing functionality.
Use global variables to store the created favourite items and the max budget. Use a class to model the wishlist item.
NOTE:
If you want to show assets (i.e. images) on your views, you will need to create a public
folder and store your assets there.
Exercise 2
Now, use a database to store the created favourite items. Use the pg
gem as the interface to interact with a PostgreSQL database.
Exercise 3
Let's add a search input (and endpoint) that filters the wishlist items based on their name.
Ruby on Rails
Ruby on Rails is a web application development framework written in the Ruby programming language. Please read the rails getting started to learn about the framework.
Intro exercise: Hotwire
DHH (Rails creator) recently introduced the Hotwire framework, which is an alternative approach to building modern web applications without using much JavaScript. It has three parts: Turbo, Stimulus, and Strada.
Turbo and Stimulus are now deeply intergrated into modern Rails versions. Follow this tutorial to know more about Rails and Hotwire.
The Odin project has some good reads, like A Railsy Web Refresher, so we recommend to investigate those resources too.
Exercise
We will now implement a complete platform for companies to organize events. Please check the platforms requirements and their estimates.
Create an account on Trello and add all the user stories to a board. We will use Trello to keep track of the progress and communicate any possible question to the client. In this exercise the client is eagerworks.
In the Trello board we will have 5 columns: Backlog, Current Sprint, In Progress, To Check, Done.
The Backlog
has all the platform user stories. The Current Sprint
column has the user stories selected for this sprint iteration. Every time we start developing a new user story we need to move that user story to the In Progress
column. We move to the To Check
column when the user story is ready to be tested by the user. If the client validates the user story she/he moves the card to the Done
column.
You can check the UI on Figma.
How to test your Rails app
RSpec
RSpec is a testing tool for Ruby, created for behavior-driven development (BDD). It is the most frequently used testing library for Ruby in production applications. Even though it has a very rich and powerful DSL (domain-specific language), at its core it is a simple tool which you can start using rather quickly.
Apart from the Rspec gem we will use the following tools:
Database Cleaner
Before each test case we need to make sure that our database is clean. If we leave information on the database, this information can cause unwanted failures.
FactoryBot
To test our platform we need data. There are two ways to create data for our tests without creating everything manually: Fixtures and Factories (Please look up the difference). At eagerworks we use factories to create our test data.
Faker
We have factories but adding emails, names, addresses to our factories can be a pain in the ass. That is why we use faker
gem to make it easier.
Shoulda Matchers
Shoulda Matchers provides one-liners to test common Rails functionality. We will use Shoulda Matchers to test model associations and validations.
Capybara
Capybara helps us test web applications by simulating how a real user would interact with your app. This tool will provide a test server for us to use on our tests.
Selenium
By default, Capybara uses the :rack_test
driver, which is fast but limited: it does not support JavaScript, nor it is able to access HTTP resources outside of your Rack application, such as remote APIs and OAuth services. To get around these limitations we will use Selenium driver.
Simplecov
SimpleCov is a code coverage analysis tool for Ruby. We will use this tool to make sure that most of our code is being tested.
Step by Step
1) Create your factories
To start testing our Rails app we need data. We need to create factories for each model that we have on our Rails project.
spec/factories/user.rb
FactoryBot.define do
factory :user do
client
name { Faker::Name.name }
email { Faker::Internet.email }
phone_number { '099999999' }
password { Faker::Internet.password }
validated { true }
end
end
2) Test your models
All the logic of our Rails app uses the defined models. We need to make sure that our models work as expected before testing anything else. When testing a model it's very important to check that the factory that we created for that model is valid. The next step is to test the model validations and associations. Finally we need to implement unit tests for each of the model's method.
require 'rails_helper'
RSpec.describe User, type: :model do
it 'has a valid factory' do
expect(build(:user)).to be_valid
end
describe 'associations' do
it { is_expected.to have_many :purchases }
end
describe 'validations' do
it { is_expected.to validate_presence_of(:first_name) }
it { is_expected.to validate_presence_of(:last_name) }
it { is_expected.to validate_presence_of(:phone_number) }
it { is_expected.to validate_presence_of(:email) }
end
describe '#method' do
end
end
3) Test your services
Implement unit tests for your services.
4) Feature tests
Feature specs are high-level tests meant to exercise slices of functionality through an application. They should drive the application only via its external interface, usually web pages.
Feature specs don't have to pay too much attention to HTML structure. They need to validate that your app features are working as expected.
For example, let's test the login feature:
require 'rails_helper'
RSpec.feature 'user login', type: :feature do
let!(:user) { create(:user, password: 'specspec') }
context 'with valid credentials' do
it 'redirects logged in user to categories index' do
visit '/users/sign_in'
fill_in 'user_email', with: user.email
fill_in 'user_password', with: user.password
click_button 'Log in'
expect(page).to have_current_path(categories_path)
end
end
context 'with invalid credentials' do
xit 'shows login error'
end
end
end
Node
In this section you are going to learn about the Node.js framework and React.
Introduction
In the following sections of the onboarding, we will cover multiple technologies. We will also provide some useful resources to help you learn about them. It's normal to feel overwhelmed at first by the number of technologies and tools we will be using, but the idea here is for you to grasp the main concepts of each one.
Node.js
Node.js is an open-source JavaScript runtime environment that runs on the V8 engine (V8 is the JavaScript execution engine that was initially built for Google Chrome. Written in C++, V8 compiles JavaScript source code to native machine code). We use the V8 engine to execute JavaScript code outside a web browser. Node.js represents a "JavaScript everywhere" paradigm, unifying web-application development around a single programming language, rather than having different languages for the frontend (client-side) and the backend (server-side). Node.js operates on a single-thread event loop, using non-blocking I/O calls, allowing it to support tens of thousands of concurrent connections without incurring in the cost of thread context switching.
Resources:
Rest API and React
When an application starts to grow, it's common practice to split the frontend (client part) from the backend (server part). If the application is big enough, this usually helps with maintainability.
In this section we are going to create a REST API to manage our wishlist items. We are going to use Fastify.
Fastify is a web framework for Node.js designed for high performance and low overhead. It provides a robust set of features while maintaining speed and efficiency.
Exercise 1 - Fastify
Using Fastify, let's create REST endpoints under the /api
namespace to manage our wishlist items.
To do so, we will use Prisma
as our ORM. Prisma is a modern database toolkit that makes it easy to work with databases. It provides a type-safe API to query the database and auto-generates the SQL queries.
Here are some useful resources:
Exercise 2 - Enhancing the API
After the basic CRUD operations are implemented, let's add some more features to our API. As a user I want to be able to filter the wishlist items by name. Let's add a new endpoint that allows the user to do this.
Exercise 3 - React
After creating the REST API, let's create a React application to consume it. To make the requests to the API more performant, we can use tools like TanStack Query. As they describe it in their website "TanStack Query (FKA React Query) is often described as the missing data-fetching library for web applications, but in more technical terms, it makes fetching, caching, synchronizing and updating server state in your web applications a breeze.".
If you haven't worked with React before, be sure to check this section first.
Next.js
Next.js is a React framework for building full-stack web applications. You use React Components to build user interfaces, and Next.js for additional features and optimizations.
Under the hood, Next.js also abstracts and automatically configures tooling needed for React, like bundling, compiling, and more. This allows you to focus on building your application instead of spending time with configuration.
Exercise 1 - Getting Started with Next.js
In the latest versions of Next.js and React, we were introduced to the concept of RSC (React Server Components). This is a new feature that allows us to render React components on the server. This opens up a lot of possibilities for server-side rendering, like faster page loads and better SEO. But it also introduces some new challenges, mostly to people that are just starting with React. Therefore, the idea of this exercise is to get you familiar with all these features, and to clearly understand what is happening in the client and what is happening in the server.
We will follow Next.js' tutorial to create a simple dashboard application. This tutorial will help you understand the basics of Next.js, React, RSC and Server Actions.
Exercise 2 - PlanIt Platform
We will now implement a complete platform for companies to organize events. Please check the platforms requirements and their estimates.
Methodology
Create an account on Trello or any other ticket management tool, and add all the user stories to a board. We will use this to keep track of the progress and communicate any possible question to the client. In this exercise the client is eagerworks.
In the board we will have 5 columns: Backlog, Current Sprint, In Progress, To Check, Done. The Backlog
has all the platform user stories. The Current Sprint
column has the user stories selected for this sprint iteration. Every time we start developing a new user story we need to move that user story to the In Progress
column. We move to the To Check
column when the user story is ready to be tested by the user. If the client validates the user story they move the card to the Done
column.
You can check the UI on Figma.
Tools
To do this exercise we will use the T3 scaffolding tool. This tool will help us to create a Next.js application with all the necessary configurations to start developing the platform. We want to use the app router, Tailwind CSS for styling and Prisma as our ORM. For authentication we need to select None
for now since we don't want to use NextAuth.js. We will use LuciaAuth for authentication. Lastly we will use tRPC to create our API.
tRPC (TypeScript Remote Procedure Call) is a framework that enables developers to build fully type-safe APIs without the need for a separate schema definition language or code generation. By leveraging TypeScript, tRPC ensures that the types of the server and client are synchronized, allowing developers to catch type errors at compile time rather than runtime. This leads to a more robust and maintainable codebase. tRPC simplifies API development by providing a straightforward, declarative way to define API routes and procedures directly within TypeScript. Additionally, it is strongly based on TanStack Query (formerly React Query), making it highly efficient for data fetching, caching, and synchronization in modern web applications. Its lightweight design and focus on type safety make it an attractive choice for projects where strong typing and developer productivity are paramount. This is really important to understand, because sometimes tRPC's doc is not super clear, and we can get lots of extra information from TanStack Query's documentation. There is a great video by Matt Pocock that teaches some tRPC basics in 5 minutes, you can check it here.
Some more useful resources:
GraphQL
GraphQL is a query language for APIs. It provides an approach to developing web APIs and has been compared and contrasted with REST and other web service architectures. It allows clients to define the structure of the data required, and the same structure of the data is returned from the server, therefore preventing excessively large amounts of data from being returned. GraphQL is a specification, not an implementation, this means that GraphQL isn’t actually a technology but a set of rules and concepts.
Lets do some research regarding data over-fetching and under-fetching in relation to the REST Architecture. How can both of these issues be addressed using GraphQL?
GraphQL Resources:
- GraphQL in 100 seconds
- GraphQL vs REST
- GraphQL: The Big Picture
- GraphQL Docs
- GraphQL: The Documentary
Exercise 1
In this part of the onboarding the idea is to replicate the same reality than in the previous exercise, but this time, instead of having a REST API built with Node.js and Express, we will be building a GraphQL API making use of Node.js and Apollo Server.
Here there are some resources that might be interesting to skim through in order to learn about GraphQL and Apollo Server:
- Building a GraphQL API with Apollo Server
- GraphQL API with Apollo Server: Getting Started
- GraphQL API with Apollo Server: Schema Basics
- GraphQL API with Apollo Server: Resolvers
In order to complete this part of the onboarding, the following must be done:
- Define types and create resolvers for them, so a client querying your API will be able to execute the following queries:
- fetch a user/tweet/comment given its id
- fetch all users/tweets/comments
- create a user/tweet/comment
- update a user/tweet/comment
- delete a user/tweet/comment
Take into account that tweets, users and comments are not isolated concepts, so again, relationships have to be defined between them. As an example, the types should be defined in such a way that your server understands that a user has many comments, or that a tweet belongs to a user.
If all of these relationships are implemented correctly, once finished this exercise, the GraphQL API must be capable of resolving the following query:
query {
comments {
tweet {
text
user {
name
}
}
}
}
Exercise 2 - N + 1 Problem
In order for the problem we will be discussing in the next section to became obvious, it'll be nice to have more than 6 users on the database. So first, lets add more users. Once this is done, lets execute the following query and watch the logs of the server.
query {
users {
tweets {
text
}
}
}
How many queries to the database are being made? Definitely too many. This is called the N+1 problem.
Opening/Closing database connections is an expensive process, so it doesn't seem to be appropriate to open so many connections to a database to fulfill simple queries. Therefore, in this part of the onboarding we will be researching about the N+1 problem and finding a way to solve it in the context of GraphQL.
Once finished one should be able to answer these four questions:
- What do we mean when we say N+1?
- Why does this problem occur?
- Which is most accepted conceptual strategy to solve it?
- Which utility do we use to solve it in the context of GraphQL and Node.js?
After all the reasearch made, one should already have found a tool to solve the problem that can be applied to the project. So now it's time to incorporate this utility into the project we are building. Once incorporated, the system should be able to resolve the GraphQL query presented above in exactly 2 queries to the database.
GraphQL Federation
TIME TO SCALE THINGS UP!
Monolithic systems, as the one we've been building, are difficult to manage when the system itself or the number of team members working at it starts to grow. So, a common practice once a system scales up is to migrate it to what we call a micro service architecture. Basically, migrating to a micro service architecture consists in dividing our entire system into small pieces (services), so each service is now in charge of a different thing. The main benefits of migrating to a micro service architecture are the following:
-
Improved Scalability: The capacity of each microservice to run autonomously makes it relatively easy to add, remove, update, and scale individual microservices. This can be done without disrupting the other microservices that compound the application. Also, when demand increases, you only need to upgrade or divert more resources to the microservice affected by the increasing demands.
-
More Resilient Applications: With a microservices architecture, the failure of one service is less likely to negatively impact other parts of the application because each microservice runs autonomously from the others.
-
Programming Language and Technology Agnostic: When creating a microservices-based application, developers can connect microservices programmed in any language. They can also connect microservices running on any platform. This offers more flexibility to use the programming languages and technologies that best fit the needs of each of the services and the teams involved.
-
Faster Time to Market: The pluggability of a microservices application architecture allows for easier and faster application development and upgrades. Developers can quickly build or change a microservice, then plug it into the architecture. Moreover, due to the independence of each microservice, teams don't have to worry about coding conflicts, and they don't have to wait for slower-moving projects before launching their part of the application.
As everything in life, it has its disadvantages too:
-
You will add communication between parts of your system (services), that will need to communicate between them via an API. This adds a point of failure.
-
Your teams start to loose knowledge of the system as a whole, they only know specific parts of it.
So, as we've seen, dividing our system into different services is a great choice when things start to scale. However, clients that consume APIs don't want to interact with different services depending on the task they want to fulfil, or to interact with multiple services and then gather the information themselves. Instead, it's much more simple for a client to interact with a unique interface. The interface would be responsible to interact with each of the services, gather the data that comes from them and then finally respond to the client. This means that the interaction between the exposed interface and each of the services completely transparent to the client consuming the API.
A common approach that allow clients of APIs to interact with a unique interface, is to create what we call a gateway. A gateway stands in front of all the services, and its responsible for exposing them as a hole, so clients can interact with the system through a unique interface.
In our case, since we are building a GraphQL API, this gateway should help us access all of our system's data by typing a single GraphQL query, even if that data lived in separate places. To make this migration to a micro service architecture we will be using Apollo Federation. An Apollo Federation architecture consists of:
- A collection of subgraphs (usually represented by different back-end services) that each define a distinct GraphQL schema
- A gateway that uses a supergraph schema (composed from all subgraph schemas) to execute queries across multiple subgraphs
Apollo Federation is often described as a way to scale your GraphQL APIs, and a big part of this is team management. The federated architecture allows teams in charge of different parts of the schema (different services) to focus on their own types without being distracted by details of the domain that aren't pertinent to them.
Here you have some interesting videos you might want to check before moving to the next exercise:
Exercise 3 - Federated Architecture
Next we'll be migrating our project to a micro service architecture making use of Apollo Federation. In order to complete this excercise, you have to do the following:
- Implement the following 3 services: a users service, a tweets service, and a comments service. Each of the services should now be in charge of a different part of our GraphQL schema and must have its own database. Also, each of the services must have their own
package.json
. - Implement a gateway so clients can consume our API through it, without interacting with each of the services directly.
In preparation for this exercise, first go to the Apollo Federation documentation and take a look at the Separation of Concerns section, this should help you decide the following things:
- How to divide your schema into different pieces.
- Which of the services should be in charge of each of those pieces.
Resources:
React
In this section you are going to learn about the React framework.
Introduction
React is a JavaScript library for building user interfaces. It is maintained by Facebook and a community of individual developers and companies.
It uses a component-based architecture that helps us build complex UIs from small and isolated pieces of code called "components", following a declarative approach. This makes the code easier to understand and maintain, allowing us to build more robust and complex applications.
Why React?
React is the most popular JavaScript library among frontend developers. It has a growing ecosystem and a strong developer community that makes it easy to find solutions for most problems.
It is also used by many companies, including Facebook, Instagram, Vercel and many more.
This, plus the fact that it is open source, free to use and has an amazing developer experience, makes React a no brainer for most frontend projects.
Concepts
React either implements or uses a lot of abstractions over vanilla web development. This makes it easy to get lost in the sea of concepts and terminologies.
In this section we will cover the most important concepts and terminologies that you need to know to get started with React.
JSX
JSX is a syntax extension to JavaScript. It is similar to a template language, but it has full power of JavaScript.
By being similar to HTML, JSX makes it easier to write and read markup code in React (and other frameworks that use JSX).
In the scope of React, JSX produces React "elements"
Some examples are:
const element = <h1>Hello, world!</h1>;
We can also add JavaScript expressions inside JSX by wrapping them in curly braces.
const name = 'John Doe';
const element = <h1>Hello, {name}</h1>;
And finally we can attach listeners to elements.
const Example = () => {
return (
<div>
<button onClick={() => alert('Hello world')}>Click me</button>
</div>
);
};
Note that JSX is not a requirement to write React applications. You can use React without JSX, but you would be losing a lot of the developer experience that JSX provides.
Components
Components are the building blocks of React applications. They are small, reusable pieces of code that return a React element to be rendered to the page.
They can be written as a function or a class. However, the class declaration is old fasion and should be avoided.
// Function component
const Example = () => {
return <div>Example</div>;
};
// or using implicit return in arrow functions
const Example = () => <div>Example</div>;
State
State is a plain JavaScript object that contains some information that may change over the lifetime of a component.
When a state is mutated react will re-render the component and all its children.
In the following example we have a state called count
that is initialized to 0
. When the button is clicked, the state is updated and the component is re-rendered.
Note that for the example we are using a hook to declare state. We will cover hooks later in this section.
const Example = () => {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
};
VDom
React uses what is called a "Virtual DOM". This is a basically a JS object that represents the DOM.
With this VDom we can compare the current state of the DOM with the desired state of the DOM, allowing react to only update the necessary of our application (sort of).
An easy way to imagine the VDom is like a tree data structure. Each component in the tree can have multiple children.
When a new state is set, React will compare the new state with the old state and update the DOM by checking in the VDom what nodes in the tree need to be updated and then update all the branches that contain the node (and the node itself).
VDom Example
If you want to learn more about the VDom, you can check out this article from Mosh Hamedani.
Hooks
Hooks are functions that let you "hook into" React state and lifecycle features from function components.
The most common hooks are useState
and useEffect
.
We sort of already saw useState
. It is used to create a state in a functional component and providing the current value and mutating function.
useEffect
is used to perform side effects in function components. By listening to values in certain states we can determine if we want it to be executed.
const Example = () => {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
// The dependency for this hook is count, meaning that it will only be executed
// when the count state changes
}, [count]);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
};
One more important thing about useEffect
is the return statement. If we return a function from the useEffect
hook, it will be executed when the component is unmounted or when the component is re-rendered and the dependency is changed.
const Example = () => {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
// The dependency for this hook is count, meaning that it will only be executed
// when the count state changes
return () => {
console.log('Cleaning up');
// This function will be executed when the component is unmounted
// or re-redendered
};
}, [count]);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
};
We recommend you read React's official documentation to learn more about hooks. You can also check out the beta React documentation for shorter and more concise documentation.
Props
Props are the way components receive data from their parents. They are passed as attributes to the component.
const Example = ({ name }) => {
return <div>Hello {name}</div>;
};
const App = () => {
return <Example name="John" />;
};
They are also one way data flow. This means that the parent component can pass data to the child component, but the child component cannot change the data.
All React components must act like pure functions with respect to their props.
In practice
Let's start with an example. We will be building a To-Do List.
Our reality is pretty simple:
- Each task in the ToDo list has a title and a description
- Each task can be marked as completed or not
- Each task can be deleted
Exercise 1 - To-Do List with React
In this exercise, we will build a To-Do List that emulates the previous reality. To achieve this, we will be making use of React and Typescript.
To create a React app, let's use Vite:
pnpm create vite
And select the react template with typescript.
⚠️ DO NOT USE CRA (create-react-app) TO CREATE A REACT APP. There are multiple reasons discussed in this issue why CRA is not a good choice for a react app.
Valuable resources to solve this exercise:
To complete this exercise, the following must be made:
- Implement a form that allows the user to create a new task.
- Implement a list that shows all the tasks.
- Implement a button that allows the user to mark a task as completed.
- Implement a button that allows the user to delete a task.
- All tasks will be saved in a state.
Excercise 1.1 Make it pretty (or pretty enough)
Now that we have a working To-Do List, let's make it look pretty. For this, we will be using Tailwind CSS.
The main goal here is:
- Learn how to add tailwind to a React app
- Learn how to use tailwind classes in a React app
- Learn how we can abstract components like buttons and inputs to make them reusable
We recommend organizing your components using atomic design. You can read more about it here.
Excercise 1.2 Use React Dev Tools
We recommend using React Dev Tools to inspect the components and the state of the application.
As a quick exercise, try to find the application's state in the React Dev Tools and also check the performance tab to see how many times a component rerenders when the state changes.
Global state management
Up to this point, we have been using React to build our applications. However, we have been using local state to manage the data. This is not a problem for small applications, but as the application grows, it becomes harder to manage the state. This is where global state management comes in.
When we have multiple components that need to access the same state, global state managers allow us to create a single source of truth for our application.
There are multiple libraries that allow us to manage the state of our application. The most popular ones are:
Redux has been the most popular choice for a long time, but recently other libraries have been gaining popularity. Zustand
is a fantastic alternative to Redux
when we don't want to write all the boilerplate that Redux
requires. Jotai
is a new library gaining popularity and allows for more atomic state management if your application requires it.
Generally, Redux
and Zustand
are the best options for state management. Redux is an excellent choice if our application also requires complex API calls by pairing it with RTK Query
.
Some may argue that another alternative is to use React Context
. This is a valid option but only in some scenarios. A Context depends on a local state of React. This means that if we want to mutate the Context, we would be re-rendering the whole component tree under the Context. This is fine if we only read data and make very few changes to the state.
Another problem that global state management (and React Context) solves is the problem of prop drilling. If we have a component 5 levels deep in the component tree and want to pass some data to it, we would have to pass it through all the components in between.
If you made multiple components in the previous excercise, you might have experienced this.
Exercise 2 - To Do List with global state management
We will reimplement the previous exercise using global state management (either Redux or Zustand).
Valuable resources to solve this exercise:
To complete this exercise, the following must be made:
- Implement a form that allows the user to create a new task.
- Implement a list that shows all the tasks.
- Implement a button that allows the user to mark a task as completed.
- Implement a button that allows the user to delete a task.
- For all actions (create, delete, mark as completed), the list must be updated in the client using redux|zustand.
- Maintain the same UI as the previous exercise.
Exercise 2.1 - Let's improve the UI even more
Some users are very picky with their UI. They either want it on light mode or dark mode. Let's add a button that allows the user to change the application's theme by changing a state using the React Context.
To complete this exercise, the following must be made:
- Implement a button that allows the user to change the application's theme.
- The state is accessed using the React Context.
- The button must change the theme of the application.
- The theme must be persisted in the local storage.
Valuable resources to solve this exercise:
Introduction
The previous two exercises did not use any external API. This is actually not a common scenario in the real world.
When using APIs we need to manage our application's state and synchronize it with the API server. This is a challenging task and requires a lot of boilerplate code.
For this reason, it's better to use a library that allows us to manage the state of our application and synchronize it with the API. Examples of this are RTK Query and React Query. The former is a library that is part of the Redux Toolkit, and the latter is a standalone library.
Exercise 3 - To-Do List with RTK Query
We will now introduce an API that will be used to persist the tasks.
Using the RTK Query library, implement the following:
- Implement a form that allows the user to create a new task and persist it in the API.
- Implement a list that shows all the tasks and fetches them from the API.
- Implement a checkbox allowing the user to mark a task as completed and persist in the API.
- Implement a button that allows the user to delete a task and persist it in the API.
Use the dummyjson
TODOs API to fetch and persist the tasks.
You can check docs for the API here.
Use invalidation of data to update the list of tasks after a task is created, updated, or deleted. Alternatively, you can update the list of tasks in the API handlers for the mutations.
Alternative
If you want to avoid using Redux
and RTK Query
, you can use React Query
instead. This great library allows us to fetch data from an API and manage the state of the data.
This is actually the preferred way to manage API calls at eagerworks since it works very well with other libraries we will see in the future.
It is almost a global state management library, but its focus is synchronizing the state with the server.
Testing
Now that we have a working application, we must ensure it keeps working as we add new features. This is where testing comes in. Testing is a way to make sure that our code is working as expected. It is a way to ensure we keep functionality intact when adding new features.
We will use Jest to test our React application since it's a widespread tool. Jest is a testing framework focusing on simplicity. It is straightforward to use, and it has a lot of features.
Unit testing
As the name suggests, unit testing is a way to test a single unit of code. This unit can be a function, a class, a component, etc. The goal of unit testing is to make sure that the unit of code is working as expected.
Let's take a look at an example. Let's say we have a function that adds two numbers together:
function add(a, b) {
return a + b;
}
We can test this function by calling it with different values and making sure that the result is what we expect. For example, we can test that add(1, 2)
returns 3
:
test('adds 1 + 2 to equal 3', () => {
expect(add(1, 2)).toBe(3);
});
The test
function is provided by Jest. It takes two arguments: the name of the test and a function that contains the test. The function that contains the test is called a test case. In this case, we are testing that add(1, 2)
returns 3
.
Setup
Let's first install jest in our react application.
pnpm install --save-dev jest babel-jest @babel/preset-env @babel/preset-react @babel/preset-typescript
pnpm install --save-dev @testing-library/react @types/jest jest-environment-jsdom
pnpm install --save-dev react-test-renderer @types/react-test-renderer
Then, let's create a file called jest.config.cjs
in the root of our project with the following content:
module.exports = {
testEnvironment: 'jest-environment-jsdom',
setupFiles: ['./jest.setup.js'],
};
Then, let's create a file called jest.setup.cjs
in the root of our project. You can leave empty for now.
Finally, let's create a file called babel.config.cjs
in the root of our project with the following content:
module.exports = {
presets: [
['@babel/preset-env', { targets: { esmodules: true } }],
['@babel/preset-react', { runtime: 'automatic' }],
'@babel/preset-typescript',
],
plugins: [
// This plugin is needed to make Jest work with vite meta import statements
function () {
return {
visitor: {
MetaProperty(path) {
path.replaceWithSourceString('process');
},
},
};
},
],
};
Now we can add the test script in our package.json file:
{
"scripts": {
"test": "jest --watchAll"
}
}
Now let's start testing.
Exercise
Let's create a tests for:
- The button atom (create one if you don't have one)
- Test that the button is rendered with snapshot testing
- Test that the button is rendered with the correct text with snapshot testing
- Test that the button is rendered with the correct color for each variant with snapshot testing
- Test actions of a reducer (without rtk query)
- Test a component that uses rtk query reducer
Useful links
NextJS
NextJS is a framework that allows you to build React applications with server-side rendering. It is a very powerful tool that allows you to build very fast applications.
Server side rendering
Server side rendering is a technique that allows us to render the HTML of a page on the server instead of the client. This allows us to have a faster initial load of the page and also allows us to have a better SEO since the search engines can index the content of the page.
Exercise 4 - Migrating to NextJS
Migrate the todo list application to NextJS.
The application should have the same functionality as the previous exercise but with server side rendering and other NextJS features (i.e. asset pipeline, env variables, and built in components).
Career development
In the following sections, you'll learn about the must-have knowledge to become a senior developer.
Common
One of the things that really speaks about someone's experience is how they are able to make good "business" decisions. Here are some examples:
- Do we need to delete users or is a soft-delete better for our clients?
- Do we need to build that feature from scratch or can we integrate with a pre-existing service?
- Do we need to create our own user authentication or can we just use OAuth/Cognito/etc?
- Do I need to optimize every method for performance? What parts of the app really need it? Were's the trade off between performance and maintenability?
- Understand when to use relational and non-relational databases depending on the use case. Do we need ultra fast reads/writes? Is data never updated? Do I care if data is lost?
- When to normalize o de-normalize data
- When to use a frontend framework and when not
The following is a non-exhaustive list but outlines some key areas a senior developer should have work experience in or have a strong understanding of:
Source control (Git)
- About version control. Chapters 1 and 2
- The Three States (or trees) of Git: the working tree, the staging area (or index), and the Git directory (or repository).
- How git fully mirrors the remote repository. The .git folder
- Different ways of working with git: Gitflow vs Trunk based development
- Branches
- Good knowledge of basic commands:
add, commit, diff, log, show, reset, checkout, branch, pull, push
git reset
: soft vs hard- Remotes. Using multiple remotes
- Merge
- Rebase. Introduction Sequence - 4 and Moving Work Around - 2
- Rewriting history. Amend and rebase
- Stashing changes
- Pull request workflow
- Cherry picking
- Merge commit vs squash and merge vs rebase and merge
.gitignore
- How to write commit messages
- Verification (e.g. PGP signatures)
- Hooks
- Undoing things & recovering from mistakes
- Resolving conflicts
Basics of algorithms
- Recursion
- Breadth First Search (BFS)
- Depth First Search (DFS)
HTML
CSS
- Selectors
- Display property
- Float
- Positioning elements
- Flexbox
- Responsive design
- SASS vs CSS
- Variables
- Nesting
- Partials
- Modules
- Mixins
- Inheritance
JavaScript
- Arrow functions vs normal functions
- Promises
async
/await
- modules
this
Testing
- The testing pyramid:
- Unit tests
- Mocks, stubs, and spies
- Creating test data with factories
- Integration tests (end-to-end)
- Dealing with third-party APIs
- Dealing with timezones
- CI and CD
- Test-driven development (TDD), test-driven design, test-first development, test-after development
Object oriented programming (OOP)
- SOLID principles:
- Single Responsibility principle
- Open/Closed principle
- Liskov substitution
- Interface segregation
- Dependency inversion
- Composition vs Inheritance
- Design patterns, including:
- Decorator
- Presenter
- Observer
- Adapter
- Bridge
- Strategy
- Façade
- Factory
- Singleton
- Template Method
- Proxy
- Command
Refactoring
- Code smells (Reek example):
- duplicate code
- dead code
- long method
- shotgun surgery
- long parameter list
- primitive obsession
- feature envy
- "Make the change easy, then make the easy change"
- Extract method
- Extract object
- Understand the dangers of premature abstraction
Performance & scaling
- N+1 queries
- Database query analysis (e.g. EXPLAIN ANALYZE)
- Database indexes
- Where to use them
- Types of indexes
- Database sharding
- Fragment caching
- Russian Doll caching
- HTTP caching (with Nginx, Varnish, etc)
- Measuring
- sample size, significance, etc
- hotspots
- CPU, memory, disk, network
- algorithmic complexity
Security
- SQL injection
- XSS
- CSRF
- Code injection
- Mass assignment
- Authentication
- Authorization
- Timing attacks
- Password complexity
- OAuth
- JWT
- OTP
- How randomness affects security
Architecture
- Inter-service communication in service-oriented systems
- Event-driven systems
- Multi-tenant systems
- Deciding if a single-page application is appropriate
- Model-View-Controller
- Model-View-ViewModel
Preparing for production
- Error monitoring
- New Relic
- Scout
- Rollbar
- Sentry
- Raygun
- Etc
- Observability (e.g. background queue size)
- Handling secrets
- ENV variables
- Encoded secrets
- AWS KMS
- TLS
- Backups (incl. DB, mid-flight processes, securing, verifying)
- Failover
- Deployment
- Heroku
- AWS
- Analytics
- A/B testing
- funnels
- bucketing
- statistical significance
- CDN
- DNS
- A
- AAAA
- CNAME
- MX
- NS
- Nginx
- understand and configure the
nginx.conf
file - usage and difference of
sites-available
andsites-enabled
file
- understand and configure the
- Load balancing and horizontal scaling techniques
Speed / Quality trade-offs
- Technical debt
- When it's appropriate to increase debt
- When it's appropriate to pay off debt
Functional programming
- Pattern matching
- Monads
- Error handling
- Non-mutability
Unix
- Process management
- Package managers
apt
- File handling
- Permissions
- The
$PATH
ENV variable - Shell scripting
- Cron: how to read and configure a cron job
- SSH commands
- Multipart messages (mixed, alternative, related, etc.)
- Protocols
- IMAP
- SMTP
Relational Databases
- How to write an Entity Relationship Diagram (ERD) and how that maps to a database table
- Locking
- Table and Row level locks
- Deadlocks
SELECT FOR UPDATE
- PostgreSQL extensions
- Different types of
JOIN
s - Aggregating data
- Views
- Materialized views
- Schema design and normalization
- Constraints
- Indexing
- Triggers
- Stored procedures
- Storing JSON/JSONB data
GIN
indexes
- Handling time and time zones
- Handling geographic data
- Understand the difference between relational database (SQL) vs non-relational (NoSQL). Understand Pros/Cons and when to use:
- Redis
- Mongo/DynamoDB
- Athena
- ElasticSearch
- Graph databases
- Time-series databases
GraphQL
- Schema design
- Type generation
- Avoiding N+1 queries
- Communicating errors
- Apollo
- Federation
HTTP
- Verbs, and verb safety
- Status codes
- URLs
- Cookies
- MIME types
application/x-www-form-urlencoded
multipart/form-data
application/json
- Headers:
Cache-Control
Etag
/If-None-Match
Last-Modified
/If-Modified-Since
Vary
X-Requested-With
React
- Components
- Hooks
- State management
- Redux, and when not to use it
- Different ways of handling forms and inputs
- Talking to a server (e.g. Apollo)
Asynchronous systems
- XHR
- Websockets
- Background jobs
- Lambda functions
- Communicating information, results and errors from asynchronous tasks
- Kafka
- SNS / SQS
- Dependencies between asynchronous tasks
Devops
- Infrastructure as code
- Cloudformation
- Terraform
- Docker
- Understand a
Dockerfile
- Understand how docker layers work
- Docker compose
- ECS
- Understand a
- Kubernetes
- EKS
Ruby on Rails
Check this first
Please review this Ruby on Rails course first and try to understand if you have any gaps in your knowledge you need to fill in.
Senior developer expectations
This is a non-exhaustive list but outlines some key areas a senior Ruby on Rails developer should have work experience in or have a strong understanding of:
- Having a good understanding of Ruby. Understand the difference between what's part of Ruby on Rails and what is part of Ruby.
- Know how to write Ruby code without rails
- Recognize and understand the differences between a
block
,lambda
, andproc
- Different types of variables: local, instance, class, global
- C optimizations for certain methods
- Understand how Rails is a collection of components/gems that work together:
- ActionController
- ActionView
- ActiveModel
- ActiveRecord
- ActionCable
- Requests/response cycle. What happens behind the scenes when a request is sent: Browser, Routing, Controller, Model, Database
- RESTful routes. https://www.theodinproject.com/lessons/ruby-on-rails-a-railsy-web-refresher
- Testing Rails Applications
- MiniTest vs RSpec
- Factories with factory_bot
- Database migrations
- Why are they useful when working on a team with multiple developers?
- How to perform a Rails version upgrade and Ruby/Rails compatibility matrix
- I18n and conventions
- Polymorphic associations, Single Table Inheritance (STI), and Delegated Types
- Naming conventions:
- filenames: always go in lowercase letters and words separated by underscore
- variables: lowercase letters and words separated by underscore
- classes and modules: PascalCase and no separator between words
- database tables: plural names with lowercase letters and words separated by underscore
- model: singular names with PascalCase
- controller: names in plural with PascalCase
- How to implement caching in Rails
- Debugging
- Benchmarking
- Find bottlenecks
- Routing
- Storing assets: ActiveStorage, Carrierwave
- Skinny controllers
- ActiveRecord callbacks
- ActionController filters
- Asset pipeline
- Hotwire: Turbo, Stimulus, Strada
- Basic metaprogramming in Rails
- Filtering elements in the database vs filtering them in memory
- Counter caches
- Background jobs and understand their differences:
- Rails API mode
- Authentication & Authorization in Rails:
- How a Rails app is typically organized. When does a JS file go to
javascript
and when to thevendor
folder? What does thepublic
folder store? - How to create a gem
- Webservers for Ruby on Rails
- Nginx / Passenger
- Puma
- Unicorn
- Thin
- PDF generation
- PDFKit
- Prawn
- How to handle payments
- Understand how payment webhooks work
- ActiveMerchant
- Eager loading and N+1 queries
- Bullet gem
- Design patterns in Rails
- Service objects
- Value objects
- Form objects
- Query objects
- View objects (Serializer / Presenter)
- Policy objects
- Decorators
- Scaffolding and when it's useful
- What does convention over configuration mean? How is it used in Rails?
- Views templating languages
- ERB
- HAML
- How development, testing, and production environments differ from each other
- How Model View Controller (MVC) works in Rails
- Model (ActiveRecord)
- View (ActionView)
- Controller (ActionController)
- Routing
- ActionCable
- Advanced usage of the Rails console
Node.js
Check this first
Please review this guide first and try to understand if you have any gaps in your knowledge you need to fill in.
Senior developer expectations
This is a non-exhaustive list but outlines some key areas a senior Node developer should have work experience in or have a strong understanding of:
- Arrow functions vs normal functions
- Modules
- using Module.export and exports
- understading the difference between both
- Asynchronous programming: blocking vs non-blocking code
- Promises
- Callbacks
async
/await
- Generators
- TypeScript
- Generics
- Node.js APIs:
- Working with files
- Events
- Streaming
- Buffer
- HTTP
- TCP/UDP
- Node.js architecture
- Handling callback hell
- Event driven programming model
- Express
- Routing
- Middlewares
req
/res
locals
- Session handling
- SQL
- PostgreSQL
- MySQL
- NoSQL
- Mongo
- DynamoDB
- GraphQL
- ORMs
- Sequelize
- TypeORM
- Prisma
- Mongoose
- Apollo
- Authentication & authorization
- Debugging in Node.js
- Testing in Node.js
- Mocha
- Chai
- Sinon
- Faker
- Use of
ENV
variables - Caching
- Redis
- Memcached
- Cluster module
this
objectsetImmediate
vsprocess.nextTick
vssetTimeout(fn, 0)
. Link- External processes
- Starting external application using
process.spawn
spawn
vsfork
vsexec
vsexecFile
- Starting external application using
- Offloading tasks that take some time to workers
- Handling JWTs in Node.js
- Handling CORS
- Architectures in Node.js
- MVC
- MVVM
- Hexagonal architecture
- Templating languages
- Mustache
- Handlebars
- EJS
- How to parallelize code with
async
- Security in Node.js applications. How to prevent:
- CSRF
- XSS (cross-site scripting)
- SQL injection
- Websockets and real-time communication
- Monolithic arquitectures vs Microservices
- Pros and cons of each one
- When to use one vs the other
- Lambda functions
- Inter service communication
- AWS SQS/SNS
- Kafka
- Package managers
- NPM
- Yarn
- Understand their differences
- Deployment and DevOps:
- Serverless framework
- Containerization with Docker
- CI/CD: Github actions, AWS Codepipeline
- Monitoring and logging in production
- New Relic
- Datadog
- Server side rendering (SSR)
- Next.js
Frontend
Senior developer expectations
This is a non-exhaustive list but outlines some key areas a senior Frontend developer should have work experience in or have a strong understanding of:
- Arrow functions vs normal functions
- Modules
- using Module.export and exports
- understading the difference between both
- Ecma module system
- Asynchronous programming: blocking vs non-blocking code
- Promises
- Callbacks
async
/await
- TypeScript
- TypeScript Utilities
- Generics in typescript
- Node.js architecture
- V8 engine
- event queue
- event loop
- thread pool
- garbage collector
- memory management
- Handling callback hell
- Event driven programming model
- Authentication & authorization
- Debugging frontend applications
- Dev tools, Redux dev tools, React dev tools, etc.
- Testing React applications
- Jest
- React Testing Library
- Faker
- Mock Service Worker
- Other testing libraries
- Object
- Object deconstruction and implications
- Object spread operator
- Object.assign
- Object.freeze
- deletions
- Use of
ENV
variables this
objectsetImmediate
vsprocess.nextTick
vssetTimeout(fn, 0)
. Link- Handling JWTs tokens
- Handling CORS
- Frontend Architectures
- Push vs pull
- MVC
- Flux
- MVVM
- Websockets and real-time communication
- Package managers
- NPM
- Yarn
- PNPM
- Understand their differences
- Deployment and DevOps:
- Containerization with Docker
- CI/CD: Github actions, AWS Codepipeline
- Monitoring and logging in production
- New Relic
- Datadog
- Sentry
- Server Side Rendering (SSR)
- Static Site Generators
- State management in React
- Redux
- Context API
- Apollo
- Recoil
- Zustand
- etc
- Caching
- Browser cache
- Server response cache
- CDN cache
- etc
- API State management
- Tanstack Query
- RTK Query
- Zustand
- etc
- Asset bundlers
- Webpack
- Rollup
- Vite
- NextJS
- Turbopack
- Preprocessors and compilers
- Babel
- TypeScript
- SASS
- LESS
- Stylus
- PostCSS
- Frameworks and their difference on how they manage state and rerenders.
- React
- React Native
- Vue
- Angular
- Svelte
- Canvas
- Web workers
- WebRTC
- WebAssembly
- Web Components
- Atomic Design
- CSS
- CSS Animations
- CSS in JS
- CSS modules
- Styled components
- CSS frameworks
- Bootstrap
- Tailwind
- Material UI
- etc
Company culture
Here you will see everything related to our company culture and how do we work.
Trabajo Remoto
Procedimientos, pautas & metodología
Contenido:
- Marco
- Equipos
- Horario - Time overlap
- Check In & Check Out
- Status – Standuply
- Tips para el trabajo remoto
Marco
Este documento tiene como objetivo definir las pautas y los lineamientos a seguir, permitiéndonos organizarnos en contextos de trabajo remoto. Consideramos que para que este esquema sea funcional a nuestra operativa requerimos por parte de cada uno compromiso, madurez y una buena autogestión.
El trabajo remoto debería ser natural, lo incorporamos en eagerWorks como un beneficio y un derecho que se gana en base a la antigüedad en la empresa y sobre todo en base a la confianza generada a lo largo del tiempo.
Podrán hacer uso de este beneficio una vez cada quince días quienes hayan cumplido seis meses de trabajo y una vez por semana todos aquellos que tengan un año de antiguedad. Todos los colaboradores que tengan dos o más años de antigüedad podrán gozarlo sin un límite de días establecido.
Para hacer uso del beneficio deben comunicarlo al menos un día antes a su team lead.
Equipos
eagerWorks puede brindar los equipos necesarios para trabajar de forma remota, coordinando y especificando vía Slack cuáles son.
Quienes prefieran utilizar sus propios equipos pueden conversarlo con su team lead.
Horario - Time overlap
Utilizaremos el horario habitual de trabajo, el cual debe haber sido coordinado de antemano con el team lead.
Consideramos que es importante respetar bajo esta modalidad el horario acordado de trabajo y el de descanso, ya que el desafío más grande de trabajar desde el hogar es establecer dichos límites. Este punto es de vital importancia para una buena gestión y manejo personal.
Para que el trabajo remoto sea exitoso, requiere de cierta superposición con las horas que sus compañeros de equipo están trabajando y con los clientes. De lo contrario, muchas veces podría implicar un retraso entre el tiempo de comunicación o respuesta.
Consideramos que necesitamos unas 4 horas de superposición para evitar retrasos en la colaboración y sentirnos como un equipo, de 11:00 a 15:00 hs.
Check In & Check Out:
Al comenzar la jornada laboral cada uno debe saludar en el canal de equipo de Slack, para dar a conocer a los demás miembros y particularmente al team lead, que se iniciaron las actividades del día y nos encontramos disponibles.
Se entiende que si uno está activo debería estar disponible para recibir mensajes, por lo que es importante mantener la aplicación abierta. Procuremos mantener tiempos de respuesta por debajo de los 15 minutos.
En caso que uno NO esté disponible, se sugiere notificar al equipo, ya sea mediante el status, o con mensajes de AFK (away from keyboard).
Al finalizar la jornada laboral cada persona comunicará que está finalizando las actividades por el día.
Status - Standuply:
Para que nuestros procesos remotos sean exitosos y para estar informados de lo que está haciendo el equipo, utilizaremos standuply mediante Slack.
La idea es especificar todas las tareas que hicimos el día anterior y las que planeamos hacer en el día. Al mismo tiempo podremos mencionar si estamos bloqueados en/por algo.
El trabajo remoto requiere de mayor seguimiento y contacto para minimizar errores, malos entendidos o sobretrabajo.
eagerTips
-
Generar un espacio físico de trabajo apropiado. Preferentemente exclusivo, con condiciones apropiadas a nivel de sonoridad, privacidad, iluminación, conectividad y ergonomía.
-
Evitar trabajar en el dormitorio, en la cocina, o en ambientes en los que es más fácil distraerse.
-
Intentar replicar el mismo setup que en la oficina (monitor, mouse, teclado, silla cómoda).
-
Establecer una rutina: en término de horarios y descansos, para evitar el riesgo de estar sobreconectado o de desconectarse demasiado seguido. Definir los horarios de trabajo y el periodo de descanso.
-
Respetar los horarios de oficina.
-
En las llamadas utilizar auriculares con micrófono para que se escuche mejor.
-
Pedir colaboración de quienes comparten tu hogar para evitar interrupciones.
-
Apagar distractores (redes sociales, correo, notificaciones de celular, etc).
-
Usar un browser dedicado para trabajo y otro para entretenimiento.
-
Vestirse como si fueras a ir a la oficina (evitar trabajar de pijamas).
-
Comer en la cocina, no en el escritorio.