Welcome to the Software pool 2024! π
During this week, you will learn the base of the modern web programming, core concept of frontend, backend, data storage... with a piece of devops.
Day purposes
Setup a NodeJS project.
βοΈ Learn the basics of Typescript.
βοΈ Discover software development good practices.
βοΈ Introduce you to design pattern, especially MVC.
βοΈ Create an interactive application using your terminal.
Typescript run using a Javascript runtime called NodeJS. To begin, install NodeJS using nvm.
It's really complex to manage Node versions manually,
nvm
exists to help you manage it without any complexity.
If you have any issue don't hesitate to ask the staff for help π
You can verify that node is correctly installed using the command below:
# Version must be at least >= 14
node -v
> v18.12.1
We will also need a package manager!
Indeed, we don't need to totally start from scratch and reinvent the wheel for some things that are already working well, we will just import it.
To do so, we will use npm (which stands for node package manager π)
You should already have it installed on your computer if you used nvm
.
# Get npm version
npm -v
> 8.19.2
You've probably already heard of Javascript,
which was very popular in the Software world, mostly for web development.
At the beginning, it was created to make web page interactive, but now you can
code almost everything in Javascript, even backend and standalone apps thanks to
NodeJS π
If you can read that page, it's thanks to Javascript that will transform markdown into human-readable text π
However, Javascript is not perfect and many cons come with it. His
weak type system, syntax liberty, undefined
variables and the lack of rigor
can lead to disasters.
An agnostic list of Javascript default is available here.
Typescript solves recurrent problems of Javascript:
- Typescript has a strong static type system.
- Auto-completion is much faster and accurate.
- Codebase are cleaner and better defined.
- Objet oriented programming is easier in Typescript than Javascript.
- Frameworks and applications are much more resilient and maintainable.
The benefits of Typescript mostly comes during development.
If you are looking for a Typescript runtime, take a look at Deno.
You've understood it, you'll learn both Typescript and Javascript today!
To begin with Typescript, it's important to understand its core concepts.
Start by reading Typescript for new programmer.
You can also take few minute to read Javascript building blocks.
Take your time to read some documentation during this day, it will help you to understand what you are doing when you will code.
It's also recommended to read more about types, functions and objects.
You can also use the official Typescript playground to test and share pieces of code π
Finally, it's important to use an IDE to help you. We recommend you VSCode or WebStorm.
You can get a free JetBrains license using your school account.
As usual, every exercises must be pushed to a git repository.
To make it easier, we will use a GitHub classroom! Follow this link to create your git repository.
You can then clone your repository.
git clone [email protected]:PoC-Community/software-pool-days-{YOUR-GITHUB-USERNAME}.git
For each day, we will create a new folder. Start with the day01
.
mkdir -p day01
It's time to start coding! We will start with a simple Hello World
program.
First, we must create a new NodeJS project and setup Typescript inside it.
To create you project, use this command from npm:
# Init your project
npm init
It will create a file named package.json
. You can consider it as a Makefile
.
It can store commands, but also register dependencies, version and special metadata related to your project.
Now, install Typescript in your project
npm install -D typescript ts-node @types/node
If you open your package.json
, you should see them in devDependencies
.
A new file named package-lock.json
should also appear. Don't remove it, it's a
file that list all dependencies required by your installed dependencies.
You should also have a node_modules
directory, it contains all the source files of those dependencies π
We use the
-D
flag to specify that our dependencies are only for development purposes. Remember, our final code will be in Javascript, so we don't need those dependencies in production.
Learn more about dependency management here.
.gitignore
to avoid pushing the node_modules
directory.
You can now create a src
directory and a file helloWorld.ts
in it.
π‘ Typescript files have the
ts
extension while Javascript files have thejs
extension.
In this file, create a function helloWorld
that will display
Hello World!
in your terminal.
Don't call it directly in this file, instead export it.
Now create a file index.ts
, then import and call you helloWorld
function inside it π
Typescript and Javascript don't have a concept of entrypoint like
C
orGo
have with themain
function.
To keep code clean, it's common to create aindex.ts
that will be the entrypoint of your program. You can also useindex.ts
in subdirectories to aggregate imports.
OK, now it's time to launch the project π₯
For this, we will set up a start
script.
There is a section called scripts
in your package.json
, it's where you
can define commands to manage your project. For example, you can run the code,
run your tests, deploy infrastructure or whatever is required to manage your project.
Write a script start
that will execute the command ts-node src/index.ts
.
π‘ Using
ts-node
enables you to directly execute TypeScript on NodeJS without precompiling it
You can run a script defined in your package.json
using npm run <script name>
.
So to run your program, type npm run start
in your terminal and press enter to see your Hello World!
message π
Discover more about npm scripts here.
Congratulation! You have run your first Typescript program π
Printing stuff is great, but a real program is composed of logic. This is what we will learn here.
Let's start with a sort numbers
!
The goal is to:
- Sort a list in ascending order
- Remove odd numbers.
- Display the result.
This way, you'll be familiar with loop, conditions, arrays and methods.
Still in src
, create the file getSortedEvenNumbers.ts
.
Inside it, create a function getSortedEvenNumbers
:
- It takes an
array
ofnumber
as parameter. - It returns a
string
composed of all even numbers in ascendant order split by a-
.
π‘ You should have a look at the sort, filter and join methods.
Update src/index.ts
to call your function and run your program to check if it's working π―
Here's an example for the following input: [1, 0, 19, 17, 16, 8, 13, 24]
.
0 - 8 - 16 - 24
You can try other combinations by manually changing your code or, you can write tests π§ͺ
It's really important to correctly test your code, a code not tested can lead to unpredictable bugs and the time lost to debug it could be avoided by creating some tests.
To do so, we will use Jest, the most popular Javascript testing tool.
Start by installing it
npm install -D jest ts-jest @types/jest
Yes, it's again a development dependencies as we won't need to run tests in production.
In your package.json
, add those scripts:
test
:jest tests/ --env=node
test:cov
:jest tests/ --env=node --coverage
test:watch
:jest tests/ --env=node --watchAll
π‘ It's common to add a subcommand with
:
to change the behavior of a command.
Now we will configure Jest.
Create the file jest.config.json
with the following content:
{
"transform": {
"^.+\\.tsx?$": "ts-jest"
},
"testMatch": ["**/tests/**/*.tests.ts"],
"moduleFileExtensions": ["ts", "js"]
}
Create a tests
directory, this is where we will write our tests.
Inside, create the file getSortedEvenNumbers.tests.ts
.
Suffix files with
<file tested>.tests.ts
are a standard way to name your unit tests.
To make sure your getSortedEvenNumbers
works as intended, you can write a test for each of these cases:
- Only positive numbers
- Only negative numbers
- Mixed numbers
Example of a test
Imagine you want to test a simple sum
function:
// sum.ts
/**
* Simply do an addition
*
* @param nbOne first number to compute
* @param nbTwo second number to compute
* @return result of nbOne + nbTwo
*/
export default (nbOne: number, nbTwo: number): number => {
return nbOne + nbTwo;
};
Your test file will be:
// sum.tests.ts
import sum from '../src/sum';
/**
* Test sum function
* Compute some number to verify if sum is working well
*/
describe('Test sum function', () => {
it('Simply 1 + 1', () => {
expect(sum(1, 1)).toBe(2);
});
it('The answers of the Universe !', () => {
const result: number = sum(21, 21);
expect(result).toBe(42);
});
});
Execute your tests with npm run test
π
npm run test
# Below is an example of result
# PASS tests/getSortedEvenNumbers.tests.ts
# Test getSortedEvenNumbers function
# β Case positive numbers (1 ms)
# β Case negative numbers (1 ms)
# β Case positive and negative numbers
#
# Test Suites: 1 passed, 1 total
# Tests: 3 passed, 3 total
# Snapshots: 0 total
# Time: 1.637 s, estimated 2 s
coverage
in your .gitignore
.
You should have understood Typescript core concept, now it's time for the most important concept and unfortunately, the hardest: asynchronicity.
β οΈ Take your time to read the links given in this part before coding. If you do not understand asynchronicity, you won't be able to understand more complex programs.
In Javascript/Typescript, your program is executed following an event loop, so NodeJS doesn't wait the end of a function before calling the next one.
It can be strange if you are used to C
but in a NodeJS program, if you run
a function which takes 5 seconds to finish and another that takes 1 second.
You'll see the result of the second before the first one π€―
This fact raises a lot of constraints because sometime, you need to get the result of a function to send it to another.
To solve this issue, the callback system appeared, but unfortunately it creates new problems and huge codebase with the callback hell.
A new concept then appear and definitely saved us from the hell: Promises.
Promises allow a function to be waited before calling the next function. To use it, we use the async/await syntax.
If you are lost don't hesitate to ask the staff for help, they'll be happy to help you understand this concept π
Let's practice: you'll create a program that displays Hello
and the name
wrote by the user in the terminal.
To retrieve inputs from the terminal, we will need to use external dependencies.
Let's install them:
# Install the prompts module
npm install prompts
# Install its types as a development dependency
npm install -D @types/prompts
Still in src
, create the file helloName.ts
.
Inside it, create a function askName
:
- It does not take parameters
- It returns the username retrieved from the terminal.
Read the documentation of prompts to read input from the terminal.
Don't forget that your function must be waited, correctly type it withasync
π
You can then create the function helloName
that will display Hello ${name}!
where ${name}
is the result of askName
.
To use
await
, your function must be asynchronous. Don't forget to handle errors.
To test your function, update src/index.ts
to call helloName
.
You should get the following result:
β What is your name? β¦ Slim Shady
Hello Slim Shady!
To see the effect of the asynchronicity, remove some
await
in your program and try again.
Before going through complex exercises, we are going to set up guardians of clean code.
There are two tools well known in Node for this:
- Eslint will set rules to make your code follow a standard. It will help to ensure the codebase is clean and spot errors before you run your code.
- Prettier set rules to keep the same coding style in the codebase, which is very useful when collaborating with other developers on a project.
We will also customize the behavior of Typescript using a dedicated config file.
Create a tsconfig.json
file with the following content:
{
"compilerOptions": {
"esModuleInterop": true,
}
}
You can learn more about why we need this option from the documentation or in this great blog post π
First, install eslint
in your development dependencies:
npm install -D eslint
Let's configure it with the CLI:
# Initialize eslint
npx eslint --init
π‘
npx
is a tool to execute a binary installed locally in a NodeJS project. For example, you can usenpx depcheck
to detect unused dependencies
Follow those steps when configuring ESLint
? How would you like to use ESLint? To check syntax and find problems ? What type of modules does your project use? JavaScript modules (import/export) ? Which framework does your project use? None of these ? Does your project use TypeScript? Yes ? Where does your code run? Node ? What format do you want your config file to be in? YAML The config that you've selected requires the following dependencies: @typescript-eslint/eslint-plugin@latest @typescript-eslint/parser@latest ? Would you like to install them now? Yes ? Which package manager do you want to use? npm
Those steps are configuring some ESLint rules, during the pool we will follow the AirBnB convention.
Let's add some rules in the package.json
to run the linter:
lint
:eslint src/**/*.ts
.lint:format
:eslint --fix src/**/*.ts
.
You will certainly find syntax errors when running it.
As you'll see, calls to console.log
are considered as a warning.
We also need to use the base convention provided by AirBnB.
To fix this, let's update our ESLint configuration with the one below:
env:
browser: true
es2022: true
extends:
- airbnb-base
- airbnb-typescript/base
- plugin:@typescript-eslint/recommended
parser: '@typescript-eslint/parser'
parserOptions:
project: tsconfig.json
ecmaVersion: latest
sourceType: module
rules:
no-console: 'off'
quotes:
- error
Make that you have the following modules in your devDependencies
:
"@typescript-eslint/eslint-plugin": "^5.42.0",
"@typescript-eslint/parser": "^5.42.0",
"eslint": "^8.27.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-airbnb-typescript": "^17.0.0",
If a dependency is missing, install it.
It's not a problem if your versions are different π
As well, ESLint extensions are available in your favorite IDE:
With those, you will see errors and warning directly in your code π€©
ESLint is ready, let's setup Prettier now!
As before, you have to add it as a development dependency:
npm install -D prettier eslint-config-prettier eslint-plugin-prettier
eslint-config-prettier
is used avoid conflicts between ESLint and Prettier rules, whileeslint-plugin-prettier
will report Prettier errors in ESLint π
When you are done, update your .eslintrc.yml
to:
- extends
plugin:prettier/recommended
- set the
prettier/prettier
rule towarn
Finally, create a .prettierrc.json
file at the root of your project and configure
it as you like!
There are also extensions available:
You are ready for a complex exercise. Let's create our first application!
It will be a program that helps you manage your favorite artists from your terminal π΅
We will not code everything in one step, that would be too huge for this moment.
Let's start with a simple implementation. The goal is to have a CLI similar to this one:
Welcome into your Artists Book!
What do you want to do?
1 - List my favorite artists
2 - Leave
# User tip input
> 3
Tip 1 or 2.
# User tip input
> 1
Here's your favorite artists:
-- 1 -- B2O
-- 2 -- SCH
-- 3 -- Laylow
-- 4 -- Billie Eilish
What do you want to do?
1 - List my favorite artists
2 - Leave
# User tip input
> 2
See you!
To do this, we must build a program that follows a strong architecture. We will use one of the most popular: MVC.
MVC stands for Model - View - Controller. It's an architecture where your code logic is split into smaller parts to easily maintain and scale a project.
We will now adapt MVC to our need, don't worry if we do not strictly follow the architecture.
Here's a schema of your architecture:
Let's code it step by step π
The router corresponds to the entrypoint of your program and the main loop.
It has different roles:
- Display actions to user.
- Catch the user input.
- Call
controllers
to execute the action asked by the user.
A good architecture also requires a good folder management in your code.
Each resource must be in its own folder with the router
making the link
between them.
- Create a directory
artistsBook
insrc
and create the filerouter.ts
in it. - Create a function
router
that will perform the loop described in the example.
You are big boy/girl now, you got the keys to do it by yourself πͺ
- Update
index.ts
to callrouter
.
A controller is in charge of the business logic that manages your resources.
Its only purpose is to create the link between the function that manage your
data storage (for now it will be a simple JSON
file) and functions exposed
to the user.
- Create a directory
controllers
in theartistsBook
folder. - Create a directory
artists
in it. - Create a file
display.ts
- Write the function
displayAll
that controls the display of the user's favorite artists
A repository is responsible for all interactions with the data storage.
- Create a directory
repositories
in theartistsBook
folder. - Create a directory
artists
in it. - Create a file
get.ts
- Write the function
getAll
that retrieves user's favorite artists
A view exposes a list of functions to the user to make it interact with a resource.
- Create a directory
views
in theartistsBook
folder. - Create a directory
artists
in it. - Create a file
display.ts
- Write the function
displayAll
that displays the user's favorite artists in the terminal.
A model defines the type of the stored data.
- Create a directory
models
in theartistsBook
folder. - Create a file
artist.ts
- Export a type
Artist
that contains a fieldname
of typestring
Data defines your storage, it can be a database, an Excel file or whatever that
can store data. Here, it will be a simple JSON
file.
JSON stands for JavaScript Object Notation. It's a standard like
CSV
,XML
orYAML
used to define a structured data.
We'll discover real databases tomorrow π
- Create a directory
data
in theartistsBook
folder. - Create the file
artists.json
with the following content:
[
{
"name": "B2O"
},
{
"name": "SCH"
},
{
"name": "Laylow"
},
{
"name": "Billie Eilish"
}
]
You will need to modify your tsconfig.json
to import .json
files π
Finally, you should have the following architecture:
artistsBook/
router.ts
controllers/
artists/
display.ts
repositories/
artists/
get.ts
views/
artists/
display.ts
models/
artist.ts
data/
artists.json
This exercise may seems hard but if you write your code step by step, it will be a piece of cake π°!
You've built the base of your MVC architecture, it's time to improve it π
For now, you can only read data, let's add operations to create, update or delete it.
Those four primitive operations are mandatory to manage a resource in a data storage. They are usually called CRUD, which stands for CREATE - READ - UPDATE - DELETE.
Let's add the missing ones π
Update your codebase to allow a user to add a new artist to his list.
Add a file create.ts
in your artists repository.
In it, write the function create
that will add a given artist in the data storage.
β οΈ You'll need to write data intoartists.json
.
Take care to not lose data when you rewrite the file.
It must have the prototype below:
function create(name: string, callback: (found: boolean, err: NodeJS.ErrnoException) => void) {}
found
must be set to true
if an Artist
match the given name.
found
help you to know what to do after callingcreate
, for example iffound
is true, you'll display<artist name> already exists!
in the terminal.
As you've seen in the function prototype, you will use a callback
.
Even if you know
async/await
and could do this with it, it's important to understand callbacks π
Now that you have updated repository
, you must update other parts of your MVC.
- Create the file
ask.ts
in theviews
directory. - Write the function
askName
that retrieves an artist's name from the terminal.
π‘ You can copy code from the precedent step to win time
- Create the file
create.ts
in thecontrollers
directory. - Write the function
create
that controls the artist addition.
Add the possibility to create an artist in router.ts
.
You can code other functions to make your code works. For example, a function to
display the artist name if it has been correctly added to the list.
This function should be in the views, in display.ts
for example.
You should have the following result:
Result preview
Welcome into your Artists Book!
What do you want to do?
1 - List my favorite artists
2 - Add an artist to my favorite
3 - Leave
# User tip input
> 2
What's the artist's name?
# User tip input
> Bob Marley
Bob Marley has been added to your favorite artists!
What do you want to do?
1 - List my favorite artists
2 - Add an artist to my favorite
3 - Leave
# User tip input
> 1
Here's your favorite artists:
-- 1 -- B2O
-- 2 -- SCH
-- 3 -- Laylow
-- 4 -- Billie Eilish
-- 5 -- Bob Marley
What do you want to do?
1 - List my favorite artists
2 - Add an artist to my favorite
3 - Leave
# User tip input
> 2
What's the artist's name?
# User tip input
> Bob Marley
Bob Marley already exists!
What do you want to do?
1 - List my favorite artists
2 - Add an artist to my favorite
3 - Leave
# User tip input
> 3
See you!
Update your application to allow the user to update an artist in his list.
Create the file update.ts
in your repository
folder.
Add the function update
to modify an artist in the data storage.
The function must follow the following prototype:
function update(name: string, newName: string, callback: (found: boolean, err: NodeJS.ErrnoException) => void) {}
β οΈ Same as create, take care when you rewrite the JSON file.
Add the function askNewName
in your view
.
I'm sure you have understood the logic π§
Don't forget to update your controller
, the router
and add anything
required to correctly update an artist.
You should have a result similar to this one:
Result preview
Welcome into your Artists Book!
What do you want to do?
1 - List my favorite artists
2 - Add an artist to my favorite
3 - Update an artist in my favorite
4 - Leave
# User tip input
> 2
What's the artist's name?
# User tip input
> Bob Marley
Bob Marley has been added to your favorite artists!
What do you want to do?
1 - List my favorite artists
2 - Add an artist to my favorite
3 - Update an artist in my favorite
4 - Leave
# User tip input
> 3
What's the name of the artist you want to update?
# User tip input
> unknown
What will be the new name of unknown?
# User tip input
> Mac Miller
unknown is not in your favorite list.
What do you want to do?
1 - List my favorite artists
2 - Add an artist to my favorite
3 - Update an artist in my favorite
4 - Leave
# User tip input
> 3
What's the name of the artist you want to update?
# User tip input
> B20
What will be the new name of unknown?
# User tip input
> Booba
B2O has been successfully updated.
What do you want to do?
1 - List my favorite artists
2 - Add an artist to my favorite
3 - Update an artist in my favorite
4 - Leave
# User tip input
> 1
Here's your favorite artists:
-- 1 -- Booba
-- 2 -- SCH
-- 3 -- Laylow
-- 4 -- Billie Eilish
-- 5 -- Bob Marley
What do you want to do?
1 - List my favorite artists
2 - Add an artist to my favorite
3 - Update an artist in my favorite
4 - Leave
# User tip input
> 4
See you!
Update your application to allow the user to delete an artist in his list.
Create the file delete.ts
in your repository
folder.
Add the function deleteFunc
to delete an artist in the data storage.
π‘ We can't use
delete
as it's a reserved word
The function must follow this prototype:
function deleteFunc(name: string, callback: (found: boolean, err: NodeJS.ErrnoException) => void) {}
β οΈ Same as before, take care when you rewrite the JSON file.
You already have the askNewName
function that you can use π
Don't forget to update your controller
, the router
and add anything
required to correctly delete an artist.
You should have a result similar to this one:
Result preview
Welcome into your Artists Book!
What do you want to do?
1 - List my favorite artists
2 - Add an artist to my favorite
3 - Update an artist in my favorite
4 - Delete an artist
5 - Leave
# User tip input
> 2
What's the artist's name?
# User tip input
> Bob Marley
Bob Marley has been added to your favorite artists!
What do you want to do?
1 - List my favorite artists
2 - Add an artist to my favorite
3 - Update an artist in my favorite
4 - Delete an artist
5 - Leave
# User tip input
> 4
What's the name of the artist you want to delete?
# User tip input
> unknown
unknown is not in your favorite list.
What do you want to do?
1 - List my favorite artists
2 - Add an artist to my favorite
3 - Update an artist in my favorite
4 - Delete an artist
5 - Leave
# User tip input
> 3
What's the name of the artist you want to delete?
# User tip input
> Bob Marley
B2O has been successfully deleted.
What do you want to do?
1 - List my favorite artists
2 - Add an artist to my favorite
3 - Update an artist in my favorite
4 - Delete an artist
5 - Leave
# User tip input
> 1
Here's your favorite artists:
-- 1 -- Booba
-- 2 -- SCH
-- 3 -- Laylow
-- 4 -- Billie Eilish
What do you want to do?
1 - List my favorite artists
2 - Add an artist to my favorite
3 - Update an artist in my favorite
4 - Delete an artist
5 - Leave
# User tip input
> 5
See you!
You have implemented a complete MVC architecture, that's excellent π
Only one thing is missing, our data is too basic, it only has a name.
Let's add some fields by updating the Artist
type:
id
: A unique identifier of typestring
top
: Best song, as astring
fans
:number
of fanslistenedTime
: the amount of time you listened to this artist, stored as anumber
of listened hours
π‘ It's common to put a unique identifier when you store data, this way, you can easily distinguish them. Several packages and implementations exist, but here we'll use the built-in randomUUID() method from the
crypto
WebAPI.
You'll have to update all your codebase to support those new fields.
Don't worry, it's not that hard because you have build a strong architecture!
And if you struggle, remember that the staff is here to help you out π
First, congratulation! You've survived day 1 π
Below you'll see two bonuses to challenge you.
Your MVC currently manages only one resource: Artist
.
You can create a new resource named Music
containing the following data:
id
: Resource unique identifierartist
: Owner of the musicname
: Music's titlelistened
: Number of listeninglink
: A link to the music (E.g:spotify
,youtube
...)
You're free to add any features to your application.
You can maybe start by adding the CRUD for your new Music
resource.
Your MVC is build with the imperative paradigm.
But actually, when you are managing resource, it's much more natural to do it in an object oriented way π
Try to do it in that way by merging your repository
and your model
into a Class.
You do not need more help if you reach this step, be strong soldier!
- Transpile Typescript to Javascript
- Typescript type explanations
- Decorators
- Templating
- Contribute to Typescript
- Type a Javascript module
π Don't hesitate to follow us on our different networks, and put a star π on
PoC's
repositories.