The junior phase will comprise of 10 sections, with each section taking roughly 1 week of classes.
- Our main curriculum will follow the Fullstack open curriculum from the University of Helsinki
- For our intro on pure react & tooling, we will follow the 'No frills react' and 'JS tools' chapters from Complete Intro to React v8 by Brian Holt
- You can use this spreadsheet to track your bootcamp progress. Click the link to make a copy, then enter your start date and expected study hours per week in the Timeline sheet. As you finish each course, update the Curriculum Data sheet with your completion date on the "Actual End Date" column. This will automatically revise the "Calculated End Date" column estimates for when you'll complete the bootcamp courses.
HTTP call
Part 0-b
WE-WILL-LEARN:
- HTTP call
PRE-WORK:
TO-STUDY:
LECTURE-VIDEO:
TO-DO:
Instructions for TO-DO
- create a new git repository called
fullstackopenin your local computer - create a repository in github to push your local
fullstackopen - create a folder called
part0insidefullstackopen - create separate files to put your sequence diagrams for exercises 0.4 to 0.6
pure react, modern react dev setup, component, state, event handler
Part 1-a: Introduction to React
WE-WILL-LEARN:
- pure react
- modern react dev setup
PRE-WORK:
TO-STUDY:
- Complete Intro to React v8: Pure react
- Complete Intro to React v8: JS tools
- PART 1-a: Intro to React
LECTURE-VIDEO:
- Pure react
- Create folder structure and files
- create folder
pure-react, thensrcinside it
- Create
index.htmlinsidesrc
- add script tag for React, ReactDOM, and index.js
- Create
index.jsinsidesrc
- use
ReactDOM.createRoot,React.createElement, andrenderto create web application using pure react
- Tooling with npm, prettier, eslint, vite
- Create folder structure and files
- create folder
tooling, then copysrcfrompure-react
- Toolings
- create npm project by
npm init -y - install Dev prettier, eslint, vite
- setup config for prettier, eslint, and vite
- install react, react-dom
- Convert pure react to dev environment running from vite
- Move react and react-dom libraries to index.js
- Use
type="module"in index.html - Create vite scripts in package.json for
dev,build, andpreview
- Using JSX
- Move components
AppandHelloto individual files
- remember to import and export required things
- make sure to start component name with
Capital
- Convert
React.createElementto JSX
- rename all files with JSX to
.jsxextension from.js - remove the imports that are not required for JSX
- Configure eslint to understand react and JSX
npm i -D eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react- update eslint config
- Please read Some notes section for common errors to avoid
- What can you render in JSX?
- Move components
Instructions for the workshops shown in the LECTURE-VIDEOs
- Create a new repository called
fullstackopen-workshops- create the repository in your local computer
- create a repository in github to push your local
fullstackopen-workshops - create a folder called
part1insidefullstackopen-workshops
- Please do the workshop at least once by yourself
- read notes under
LECTURE-VIDEOsection - watch the lecture-video (if required)
- read the material (if required)
- then put today's workshop inside the
part1folder - refer to source code from lecture in
part1-abranch if needed - continue future workshops under appropriate folder structures
- read notes under
TO-DO:
Instructions for TO-DO
- In the
fullstackopenrepository, create a folder calledpart1insidefullstackopen - Create folder called
courseinfoinside ofpart1to put your code for exercise 1.1-1.2- You can create
courseinfoproject either by usingvite, as we did for the class today. You can clone this starter kit - Or you can create
courseinfoproject usingcreate-react-appas described in theintroduction to reactsection of the course
- You can create
note-1: If you are cloning the starter kit, delete the .git directory inside the clone.
note-2: See this example exercise repository for folder structure. Further details can be found with the exercise instructions
Part 1-b: Javascript
WE-WILL-LEARN:
- Javascript concepts
TO-STUDY:
LECTURE-VIDEO:
- Array methods in React
- Create a react project that says
helloto many people
- create project
using-arrayinsidepart1 - copy the
srcfiles from part1/tooling/src
- In
Appcreate an array of objects of people to say hello to
- a person object can have the properties for
firstName,lastName,idetc.
- Display hello to each person
- for each element in array, call
SayHellocomponent through themapmethod- pass entire person object to
SayHello - in
SayHello, destructureprops - write a component helper function to return combination of
firstName,lastName - use the helper function in the JSX returned by the component
- pass entire person object to
- If array is empty, display appropriate message
- use if else condition
- use ternary
- Create a react project that says
return (
<div>
{peopleArray.length > 0 ? (
peopleArray.map((value) => <SayHello person={value} key={value.id} />)
) : (
<h1>No records found</h1>
)}
</div>
);- add
filtermethod to say hello only to people with id greater than 2
Instructions for the workshop shown in the LECTURE-VIDEO
- In the
fullstackopen-workshopsrepository created in part1-a- create the workshop project
using-arrayinside the folderpart1
- create the workshop project
- Please do the workshop at least once by yourself
- read notes under
LECTURE-VIDEOsection - watch the lecture-video (if required)
- do today's workshop in the
using-arrayfolder - refer to source code from lecture in
part1-bbranch if needed
- read notes under
TO-DO:
Instructions for TO-DO
- In the
fullstackopen/part1/courseinforepository, continue to put your code for exercise 1.3-1.5
Part 1-c: Component state, event handlers
LECTURE-VIDEO:
- Component manual re-render
- Create a react project that updates the count
- create project
counter-appinsidepart1 - create component
Appand jsx fileindex.jsx
- Update the count in some set interval
- use
setIntervalto update count, and also call the render manually to re-render the app
- React state
- Use
useStatehook to make a stateful component insideAppcomponent
- convert the counter to a state using
useState - use
setTimeoutto call a function after 1 second that changes the value ofcounterstate - remove the manual call to render
- Use
- React event handling
- add a button to increase the count
- add button element
- include an
onClickevent handler to the button. The event handler has to be a function, not function execution!
- Refactor components for display and button
- refactor
Displaycomponent- call
DisplayfromAppand also pass it the counter state
- call
- refactor
Buttoncomponent- call it for plus, minus, and zero by passing appropriate event handlers
TO-DO:
Part 1-d: Complex state, debugging React apps
WE-WILL-LEARN:
- a more complex state
- array, object in state: don't mutate state!
- conditional rendering of component
- debugging React apps
LECTURE-VIDEO:
- Using object, array in state
- Create a copy of
counter-appinpart1directory
- first delete
node_modulesfolder fromcounter-app cp -r counter-app double-counter
- Put two counters
- put the state of the left and right counter in an object
- put the state of click history in an array
- lets put the total number of clicks in a state
- Create a copy of
- Conditional rendering in component
- Create a new
Historycomponent that shows the click history - Conditionally show the history only when there is history
- otherwise, show another message
- Create a new
- React debugging, and notes on hooks
- Do the following for debugging
- console.log
- debugger
- React developer tools
- Rules of hooks (useState, useEffect etc.)
- only call hooks inside functional components
- do not call hooks from
- inside of a loop
- a conditional expression
- Exercise 1.1 to 1.14 guide
TO-DO:
Modules, forms, getting data from server, altering data in server
You can refer to the workshop code solutions here
Part 2-a: Rendering a collection, modules
PRE-WORK:
WE-WILL-LEARN:
- rendering collection [Array or Object]
- don't mutate state! especially if state is Array or Object
- Array.map
- Array.reduce
- most common problems in React up to this point
- the props are expected to be of a different type,
- or called with a different name than they actually are, and destructuring fails as a result
LECTURE-VIDEO:
- Using Array.map to show data in React
- Create react project
notes-app
- create directory
part2 - create react project
notes-appinsidepart2 - pass array of notes from
index.jsxtoApp.jsx - in
App.jsxaccess the array data using indices
- Access the array using map method
- Create react project
- Using key in React lists, and further debugging notes
- Use the
keyattribute when rendering array
- understand how map method is working
- using index vs id for
keyattribute
- Refactor the code
- destructure the props
- create the
componentsdirectory insidesrc - refactor components and put them in
componentsdirectory- note that the
keyattribute is now required in the component
- note that the
- Use the
TO-DO:
Part 2-b: Forms
WE-WILL-LEARN:
Main concepts
- controlled HTML input component
- filtering displayed elements
Side notes
- form onSubmit event handler needs event.preventDefault()
- form in App
- controlled HTML component: using onChange
- ternary expression
- Array.filter
LECTURE-VIDEO:
- Adding a controlled HTML input component to add notes
- Create a state to keep track of notes array
- Add a form in JSX to add a note
- Put an
onSubmitevent handler to the form - Add a controlled HTML input component to capture the entry of a new note
- add a new state to store the new note
- use the state as the input
value - add an
onChangeevent handler on the input
- Complete
onSubmitto add new note to notes array
- Filtering displayed notes
- Add state to keep track of
showAll - Add a
notesToShowvariable to keep notes to show based onshowAllstate
- if
showAllis true, thennotesToShowis same asnotes - else,
notesToShowonly has importantsnotes
- Add functionality to toggle between
allandimportant
- add button with text for action
- add
onClickto button to toggleshowAllstate
- Add state to keep track of
TO-DO:
Part 2-c: Getting data from server
WE-WILL-LEARN:
- getting data from backend server
- using JSON Server as our backend server
- useEffect hook
- using Axios to call server
- understanding promises
LECTURE-VIDEO:
- Setting up json-server as our backend server
- Create the
db.jsonfile with array of notes - Install
json-serveras dev dependency
npm install json-server --save-dev- Start the json-server
npx json-server --port 3001 --watch db.json- Include json-server start command in package.json scripts
- view the output on web browser
- if required, install the JSONVue extension
- Create the
- Understanding the useEffect hook
- in App.jsx, create a
useEffecthook so that it is only called on first render - update the
useEffecthook so that it executes each time theshowAllstate changes
- in App.jsx, create a
- Using Axios in frontend to access data from backend
- Install axios as dependency
npm install axios- Read the data from json-server from our app using axios
- start json-server in one terminal
- start the app in another terminal
- add code for axios.get in App.jsx inside the
useEffecthook - modify
useEffectso that it is called only on first render of the component
TO-DO:
Part 2-d: Altering data in server
WE-WILL-LEARN:
- understanding REST
- routes
- CRUD (Create, Read, Update, Delete) actions on REST routes
- axios methods corresponding to CRUD actions
- sending data to the Backend Server
Side notes
- Array.find
- Review
- Array.map
- Array.filter
LECTURE-VIDEO:
- Using axios.post to create note in backend
- Add
axios.postcode inside the event handler responsible for creating new note
- a brief look at REST API
- remove the
idfrom your new note object, because now the REST API should create the id - update your notes state with the returned note object
- now, even if you refresh, the newly added note is still present
- Add
- Using
axios.putto update note in backend- Add button to toggle the
importantfield of note
- in
Notecomponent, add a button to toggle importance - what will be the
onClickevent handling function on the button?
- In the event handling function, add logic to update the data in server, and in state
- first, find the note from our state
- then, create a new object with the changed state of the note
- don't mutate state directly!
- send the new object using axios.put to update data in server
- change the state of notes to reflect the updated note
- Add button to toggle the
- Refactoring axios services
- Create a
servicesfolder inside thesrcfolder - Create
notes.jsfild insideservices - Create a
getAll,create, andupdatefunctions specifically only for the server functions
- return a promise that returns the destructured data
- use these new functions in
App.jsxto interact with the server
- Create a
- Handling axios errors in catch block
- In
getAllnote service, add a fake note to the array returned from server - Add a
catchblock to catch any error to the call toupdate
- when we try to update a note in the server that does not exist, json-server will throw an error
- print the error in the
catchblock - if the status code is
404, it means the data does not exist- remove the data from
notesstate if data does not exist in server - show an alert with proper error message
- remove the data from
- In
TO-DO:
Part 2-e: Adding styles to React app
WE-WILL-LEARN:
- adding styles to React app
- 2 methods
- importing style file into JS project
- inline styles in JSX
- CSS rules = selector + declerations
- class selectors in JSX
- 2 methods
- error message in it's own React component
- activating error component by setting error message
LECTURE-VIDEO:
- Inline styles
- In
App.jsx, define a new variable whose value is an object with css style
- note that the CSS properties are written in camelCase, not hyphenated as in css file
- In the
h1element in the JSX, create a new attribute calledstyleand assign it the above variable
- In
- Using a css file to define a style
- Create a css file
index.cssin thesrcdirectory - Define a style for class
.redbackground - Import
index.cssinindex.jsx - In
App.jsx, use theredbackgroundclass in theinputhtml element
- Create a css file
- Creating dynamic error message from catch block
- Create a new component called
Notification
- define an
errorclass in css file to use in the Notification component
- Define a new state to put the error message with an initial value of
null - In the
catchblock for error handling, add code to display the error message in the Notification component
- in the
catchblock, set the error message state with the error message - after a certain amount of time, set the error message back to
null
- Create a new component called
- Debugging openweather map api key
TO-DO:
You can refer to the workshop code solutions here
Part 3-a: Node.js and Express
WE-WILL-LEARN:
- creating a simple Node project that runs in Node environment
- running a simple web server
- using Express library to build a more developer friendly web server
- using nodemon to run node
- defining routes in Express
- CRUD functionality in Express routes
- Middleware
- writing our own middleware
Side notes
- difference between ES6 modules vs CommonJS syntax
- what is JSON?
- what does the version number in npm library mean?
- VSCode REST client
- Math.max
LECTURE-VIDEO:
- Creating a simple express server
- Create new project
notes-server
- create an npm project inside by
npm init -y - create an http server that shows the text 'Hello world', listening on port 3001
- Serve notes as JSON data using http server
- create a notes variable with the notes array
- convert the http response to type
application/json - JSON.stringify the notes array in the response
- Create new project
- Using express for serving on the '/api/notes' route for a 'get' method request
- Install
expresslibrary
- modify code to use express library
- Modify to express route to server array of notes on
/api/notesurl forgetrequest - Install nodemon as dev dependency to run node server by hot reload on code changes
- Install
- Side note on REST and JSON
- Creating the '/api/notes/:id' route for a 'get' method request
- Create a new get route at
/api/notes/:id - Respond with the json object of the note at that id
- please note that the
request.paramsalways comes as a string
- If no notes are available at the id, then set status to 404 and return a friendly error message
- Create a new get route at
- Creating the
/api/notes/:idroute for adeletemethod request- Create a new delete route at
/api/notes/:id - Respond with 204 status code, and no body
- Install
REST Clientextension - Create file
requests/my_requests.restto store the REST requests
- call the
deletemethod on the REST endpoint
DELETE http://localhost:3001/api/notes/2 - Create a new delete route at
- Creating the '/api/notes' route for a 'post' method request
- Create a new post route at
/api/notes - Use express.json() to read json objects in the request
- Use the json object in request to create a new post in the backend
- Respond with status 201 created and return the newly created note object
- Create a new post route at
- Creating middleware
- Create a middleware at the top of the express server to log method, path, and body
- Creat a middleware at the end of the express server (just before app.listen) to send a 404 not found to all paths that are not handled by the app
TO-DO:
Part 3-b: Deploying app to internet
WE-WILL-LEARN:
- serving static files from server
- including frontend code on server
- deploying app to a cloud platform (e.g. Render)
- streamlining the deployment
Side notes
LECTURE-VIDEO:
- Use notes-server instead of json-server
- Start
notes-app - Start
notes-server - Change
baseUrlinnotes-appto the url of ournotes-server - Configure CORS in
notes-serverso that app and server work from different origins
- install
corslibrary - use
corslibrary in server'sindex.jsfile
- Start
- Serving frontend static files from node server
- Build react app for serving from web server
- Include the build folder in your node application
- Instruct node server to serve the static files from the build folder
- Modify frontend backend code to run in cloud
- In react app, change the baseurl to a relative url
- In node server, read the PORT value from environment if available
- Deploy fullstack app to Render
- Create a render account at https://dashboard.render.com/register
- sign up using
GitHub
- Create a new
Web Service
- connect to the proper github repository
- configure
Root Directory,Build Command(npm install), andStart Command
- Push your code to GitHub
- In notes-server, streamline notes-app build and deploy process
- add npm script to build the react app and copy it to server repo
- remove the existing
distfolder - cd to notes-app
- build notes-app
- cp the
distfolder from notes-app to notes-server
- remove the existing
- Add proxy to vite config of notes-app so that we can use relative url in react dev environment
server: { proxy: { "/api": { target: "http://localhost:3001", changeOrigin: true, secure: false, }, }, }
TO-DO:
Part 3-c: Saving data to MongoDB
LECTURE-VIDEO:
- Introduction to mongodb
- Setting up mongo database in mongodb.com
- Create a free account in mongodb.com
- go to https://www.mongodb.com/cloud/atlas/register
- you can sign up with google
- select the 'free' option
- Create database userid and password
- create project
- build database using free option
- choose the username password option for authentication
- finish database creation
- Set network setting to allow access from anywhere
- go the 'network access'
- edit and select 'allow access from anywhere'. note - this is only for development!
- Get the connection string to your database
- Using mongoose to set up a practice application
npm install mongoose- Create a new mongo.js file in your repo to create practice application
- copy code from fullstackopen for mongo.js
- change the url value to your db connection string
- the mongo.js code has the following:
- create a schema for Note
- use the schema to create a model for Note
- use the Note model to create and save a Note object into mongodb
- Run our code from terminal to create a collection and data in
noteAppdatabase
- run
node mongo.js password, this will create the note data intestdatabase, innotescollection - delete the
testcollection - add
noteAppto the db connection string - run
node mongo.js passwordagain, this will now create the note data innoteAppdatabase
- in
mongo.js, comment out the note creation code - add code to
getthe data - run
node mongo.js passwordagain
- Connect the notes-server to the database
- install npm library
dotenvthat will allow us to convert variables from .env file toprocess.envvariables - for localhost
- create a .env file to store the db connection string (add .env file to .gitignore)
- in .env file, enter db connection string to variable
MONGODB_URI - in index.js file, use
dotenvlibrary to read env variables
- for render: configure db connection string in render
Environment
- Create separate module to put database configuration
- Get data from database in the
/api/notesroute forgetmethod - Modify the returned data from mongoose to show the
idfield instead of_id, and hide the__vfield
- install npm library
- More node express routes configured through database
- Rewrite
/api/notesroute forpostmethod - Rewrite
/api/notes/:idroute forgetmethod - Error handling
- Moving error handling to middleware
- make sure middlewares are loaded in proper order
- Rewrite
/api/notes/:idroute fordeletemethod - Write
/api/notes/:idroute forputmethod
- Rewrite
TO-DO:
Part 3-d: Validation and ESLint
LECTURE-VIDEO:
- Mongoose schema validation
- Create a mongoose schema validation for
contentfield in noteSchema - In the note post route, catch the error in note.save
- Put the error handler in the error handling middleware
- Catch and display the error in the notes react app
- Create a mongoose schema validation for
- Mongoose schema validation while updating
- In the note update route, configure it to also throw schema validation errors
- Why schema based validation is better than logical error handling in code
- Linting setup and configurations
- Install eslint as a dev dependency
- Setup config file for eslint
- change env
browsertonode
- Setup the VSCode extensions for eslint
- Create script to run eslint
- Create eslint ignore config file
.eslintignore
- include the
distfolder
- Create eslint rule for
eqeqeq- show warning for console.log
- Difference between formatting (prettier) vs code linting (eslint)
TO-DO:
You can refer to the workshop code solutions here. Look at the appropriate branch (e.g. look at branch part4-1 for Part 4-a)
Part 4-a: Structure of backend application, introduction to testing
LECTURE-VIDEO:
- Code refactoring
- Refactor config, logger, and middleware to
utilsfolder - Split the app code from
index.jstoapp.js
- Refactor config, logger, and middleware to
- Refactoring Node express Router and Note model
- Refactor all the
notesroutes tocontrollers/notes.js
- use
require('express').Router()
- Refactor the
notesschema and model specific code tomodels/note.js
- Refactor all the
- Unit testing Node application
- Install jest in dev dependency (npm install --save-dev jest)
- Define npm script to run jest and specify the execution environment is node
- Create a file,
utils/for_testing.jswith simple functions to test - Create unit testing file
tests/reverse.test.jswith tests forreversefunction
- run it
- make one test case fail to analyze the jest error message
- Configure eslint to ignore the jest commands in the test file
- Create unit testing file
tests/average.test.jswith tests foraveragefunction
- run it and analyze the failed test case
- fix the failed case
- notice the
describeblock
TO-DO:
Part 4-b: Testing the backend
LECTURE-VIDEO:
- Using supertest
- Install supertest
npm i -D supertest - Write test cases to connect to api without starting node server, then write tests for
- get all notes
- notice async/await syntax
- regex
- no port required
- using supertest methods for test
- checking length of notes
- using jest methods
- checking content of one note
- Setup the code according to instructions in fullstackopen if timeout type errors seen in console
- include a teardown
- add Jest timeout
- set bufferTimeoutMS
- Install supertest
- Setting up test environment
- set up cross-env, if required for windows
- Set up test database for MongoDB
- create TEST_MONGODB_URI in .env file
- in config, setup the db url based on NODE_ENV
- In logger, only conditionally log if NODE_ENV is not test
- Initialize test database before test
- Include the
beforeEachblock - Modify tests written above to use the database initialization values
- Running test one at a time
- Include the
- async/await syntax
- How does async/await work
- Refactor notesRouter.get for '/api/notes' to async/await
- run tests to make sure it is still working correctly
- Write test then refactor notesRouter.post to async/await
- write the test cases to test different scenario
- run tests to make sure code is currently working
- Create helper functions for some common functions required for testing
- put helper functions in
tests/test_helper.js - refactor tests to use helper functions
- Refactor notesRouter.post to use async / await syntax
- run test to make sure code is still working after refactoring
- put try / catch block for error handling
- Optimizing the beforeEach function
TO-DO:
Part 4-c: User administration
WE-WILL-LEARN:
- Creating users with passwords in the Mongo DB database
- using
bcryptfor one way hashing of password to store in database - using Test Driven Development (TDD) method
- using
- Associate a
Noteto aUser- put a
notesarray field intoUser - put a a
userfield intoNote - association is enforced by mongoose library, not by Mongo DB
- put a
- Using the mongoose
populatemethod
LECTURE-VIDEO:
- Setting up Mongo DB for Note to User relationship
- Set up Mongoose schema for
User - Modify
NoteMongoose schema to refer toUserwho created the Note
- Set up Mongoose schema for
- Creating users
- Install bcrypt library to create one-way hash of the password
- Create new router for
usersthat handles REST api requests related tousers
- create
usersrouter - include
usersrouter in app.js - in
usersrouter, write POST method to create new user
- Write test case for user creation
- first, write
usersInDbhelper function to get all users from DB - write test case for user creation with new username, utilizing
usersInDbhelper function - running the test should pass
- Practice Test Driven Development to add functionality to creating new user
- write test case for user creation with existing username
- running the test should fail, as we expect status code 400, but as of now our code returns 201 created
- adjust create new user function to check for existing username
- use
mongoose-unique-validatorlibrary to validate for unique username
- use
- running the test should now pass
- In
usersrouter, write route handler for GET method for all users
- Update new note creation to include user that created the note
- include user._id in the created note's
userfield - append the newly created note._id to the user's
notesfield
- update GET all users route handler to
populatethe full notes that the user has created - user
populate parametersto only include the fields that we want - update GET all notes route handler to
populaterequired user fields
- include user._id in the created note's
TO-DO:
Nothing!!
Part 4-d: Token authentication
PART 4-d: Token authentication
LECTURE-VIDEO:
- Principles of token based authentication
- Understanding token based authentication sequence diagram
- Implement logging in function
- npm install jsonwebtoken
- create new router for
loginthat handles REST api requests related tologin - add the
loginrouter in app.js - add
SECRETvalue to.envfile that will be used by jsonwebtoken to sign the token - test logging in using VS Code REST-client
- Creating new notes with logged in user
- Change the POST new note handler to only allow logged in users
- Test creation of new note with token using VS Code REST-client
- write VS Code REST-client code to login
- write VS Code REST-client code to POST new note, including token returned from login
- Implement error handling
- update
errorHandlermiddleware to handleValidationErrorandJsonWebTokenError
- Problems of Token-based authentication
- Put time limit for the validity period of token
- Update
errorHandlermiddleware to handleTokenExpiredError - End notes
TO-DO:
Part 5-a: Login in frontend
LECTURE-VIDEO:
- Handling login
- In App.js, add a form for login using controlled input fields for userid and password
- Write function
handleLoginto handleonSubmitfor the login form - Write login service to call login api
- Call login service from
handleLoginApp.js - In App.js, convert login and note posting forms into functions
- Conditionally call the forms based on whether user is logged in
- Creating new notes
- Set up
notes.jsservice to use token from login response as theAuthorizationheader in the notes creation request header - In App.js
handleLoginfunction,setTokenafter login is sucessful - In
create noteservice, include theAuthorizationheader in the correct format with the token - The functionality to
add notefrom the react app should work again
- Set up
- Saving login information in the browser
- remember to use
JSON.stringifyto convert JS object to string
- In
App.js, write a useEffect hook that will read user data from local storage if available when application loads
- remember to use
JSON.parseto convert string to JS object
- What happens if saved token is no longer valid? e.g. if token expires?
- show the proper error
- remove data from local storage
- update the user state to null so that the login form appears
- remember to use
TO-DO:
Part 5-b: props.children and proptypes
LECTURE-VIDEO:
- Using props.children to create a generic Togglable component to control visibility of components
- Move login form to it's own component, LoginForm
- Move the
Togglablelogic into it's own component, Togglable
- please note how
props.childrenis being used
- Modify
App.js > loginForm()function to callLoginFormcomponent within theTogglablecomponent to make login form togglable
- Move the Note adding form (
NoteForm) into it's own component- Now use the
Togglablecomponent to toggle visibility ofNoteForm - Move new note related state to the
NoteFormcomponent
- Now use the
- Using useRef, forwardRef, and useImperativeHandle
- Using prop-types and eslint
- declare mandatory props in the
TogglableandLoginFormcomponents
- install the package eslint-plugin-jest as dev dependency
- create eslint config file, either
.eslintrc.json(copy from our previous vite repo) or.eslintrc.js(from this workshop) - add
jestrelated env, plugins entries
- Create
.eslintignorefile to ignore files where eslint should not check - Give a displayName to the
Togglablecomponent
- declare mandatory props in the
TO-DO:
Part 5-c: Testing React apps
WE-WILL-LEARN:
We will learn unit testing a React component. We will learn to test:
- a component will render what we expect it to render
- a component will call the correct function passed as a prop when a button is clicked
- a component will re-render correctly when a button is clicked
- a component will call a function with the correct argument when a button is clicked
LECTURE-VIDEO:
- Setup initial jest test for Note component
- Install the required packages
npm install --save-dev eslint-plugin-react-refresh @testing-library/react @testing-library/jest-dom jest-environment-jsdom @babel/preset-env @babel/preset-react- Make the configurations
- in package.json
- in .babelrc
- Make sure
Notecomponent has classNamenote, so that we can select by it - Write Note component test in the file src/components/Note.test.js
- Run the test. You might also need to
npm i -D jestifjestis not already installed - Modify the test to also check by selecting the element by class name in the rendered container
- Use
screen.debug()to see html output of render and of screen.getByText
- Clicking buttons in test
- npm install --save-dev @testing-library/user-event
- Write test case for button click on the
Notecomponent - Write tests for the Togglable component
- set up Togglable component for testing by adding className="togglableContent"
- now write the tests
- Testing the forms
- add className
formDivtoNoteForm
- More options for finding elements
- note if there are two input fields
- use
screen.getByPlaceholderTextto be more precise to get right text input field, or usecontainer.querySelector
- use
- using
container.querySelector - using
{ exact: false }option in thescreen.getByTextmethod - using
screen.findByText; note it returns a promise - using
screen.queryByText; note it does not cause an exception if the element is not found
- add className
- More notes on tests
- by running
npm test -- --coverage --collectCoverageFrom='src/**/*.{jsx,js}'
- by running
TO-DO:
Part 5-d: End to end Testing
LECTURE-VIDEO:
- Initial setup for cypress
- Install cypress to the frontend as development dependency (npm i -D cypress)
- Add an npm-script to run it
"cypress:open": "cypress open"
- also change dev start to
"dev": "vite --host"
- Add an npm-script to the backend which starts it in test mode
"start:test": "NODE_ENV=test node index.js" - Start cypress with command
npm run cypress:open - Create a new test file
notes_app.cy.jsinside the cypress/e2e folder - Write the first test for the front page
- Writing to a form
- type the username and password in the login form using
cy.typefor the test cy.getcommand allows for searching elements by CSS selectors- use
idto get the value from input fields and use # to select id - some things to note
- to avoid name conflicts, we gave the submit button the id login-button we can use to access it
- if you are getting eslint errors on cy
npm i -D eslint-plugin-cypress - changing the configuration in
.eslintrc.js
- type the username and password in the login form using
- Testing new note form
- Only logged-in users can create new notes, so we add logging in to the application in beforeEach block
- Give adding note input an id to avoid test failing if there is more than one input
- Each test starts a fresh browser state, as if a new browser window was opened
- Controlling the state of the database
- Challenge with E2E tests is that they do not have access to the database
- Create API endpoints to the backend for the test
- create a new router for the tests
testingRouterat backend - add it to the backend only if the application is run on test-mode
- the test does HTTP requests to the backend with
cy.request
- Write the test for changing the importance of notes
- Make a test to ensure that a login attempt fails if the password is wrong
- check error mesage
cy.get(.className).contains(message)) - note that css class selector starts with a full stop as in
.className
- For more diverse tests than contains which works based on text content only
shouldshould always be chained with get (or another chainable command)- cypress requires the colors to be given as rgb
- if the test are for same component we can chain
shouldwithand
- Bypassing the UI
- create a cy command for login
- create a cy command for adding new note
- config baseUrl
- config the env BACKEND
- Revisiting changing the importance of a note
- chaining contains with contains
- when coding tests, you should check in the test runner that the tests use the right components!
- use of
parent()andfindandasin cy
- Running and debugging the tests
- Cypress commands always return undefined
- Cypress commands are like promises
- We can run the test using cli with command
"test:e2e": "cypress run" - Videos of the test execution can be recorded
- in the cypress.config.js add
video: true; then videos will be saved to cypress/videos/ - gitignore the videos directory
TO-DO:
Part 6-a: Flux-architecture and Redux
WE-WILL-LEARN:
- create redux store
- pass the redux store to app via provider
LECTURE-VIDEO:
- Create a counter app that uses redux
- Setup the application
- clone the react starter repo
- rename to redux-counter
- cd redux-counter
- rm -rf .git
- npm i redux
- Create a simple counter app using useState in index.js (we won't be using App.jsx yet)
- create a reducer,
counterReducerin this case. the reducer does the work similar tosetState - create a store, by using
createStoreand passing it the reducer - use
store.getState()to get the store (like thestate) - use
store.dispatch(action)to modify the store (like callingsetState)actionis an object withtypekey, and optionallydatakey
- use
store.subscribe(React Component)to rerender component when store changes
- Create a note app that uses redux
- Setup the application
- clone the react starter repo
- rename to redux-note
- cd redux-counter
- rm -rf .git
- npm i redux
- Setup redux in index.jsx for note app
- create a reducer,
noteReducerin this case. the reducer does the work similar tosetState - create a store, by using
createStoreand passing it the reducer - use
store.getState()to get the store (like thestate) - use
store.dispatch(action)to modify the store (like callingsetState)actionis an object withtypekey, anddatakey
- use
store.subscribe(React Component)to rerender component when store changes
- Add functionality to toggle importance using Test Driven Development method
- setup test environment
- install required packages
- configure .babelrc file
- add test script to package.json
- add jest environment to .eslintrc.json
- add
deep-freezelibrary to test for immutability
- move the
noteReducerto its own file at reducers/noteReducer.js- export store and the reducer
- in the test file
noteReducer.test.js, put test case for adding a new post - add another test to toggle the
importantfield
- Add functionality to add new note
- Add form to add note
- Add
onSubmithandler that callsstore.dispatchfor adding note - Add frontend for toggling
- add
onClickcall totoggleImportancefunction from each note display - write the function
toggleImportanceto callstore.dispatch
- Write action creators for adding note, and toggling importance
- write an
action creatorcalledtoggleImportanceOfthat creates the action to dispatch for toggling importance - write an
action creatorcalledcreateNotethat creates the action to dispatch for adding note
- Refactor note app to use Provider
- Put the reducer in the
reducers/noteReducer.jsfile - Also move the action creators to
reducers/noteReducer.js - Move the app component to
App.js - npm install react-redux
- Modify the store related code in
index.js, pass the store toProviderand wrap theAppwith it
- in
index.jsimportProvider - wrap
AppwithProviderwithstore
- Modify
Appto readstorefromProvider
useSelectorto get access to thestoreuseDispatchto get access todispatch
- Now even if we refactor
add new noteform to its own component, we don't need to pass the store from App; the store can be directly accessed by all the components
- refactor creating new note into its own component
- Put the reducer in the
TO-DO:
Part 6-b: Many reducers
WE-WILL-LEARN:
- using
Combined reducersto combine multiple reducers - using Redux toolkit to simplify and streamline the use of Redux stores
- using Redux devtool to debug and help develop redux
LECTURE-VIDEO:
- Add state for visibility filter by putting it in another reducer using Combined reducers
- note that
NewNoteandNotescomponents have been refactored fromAppcomponent
- Initialize
notesstate with two notes innoteReducer - Create visibility filter to show all / important notes
- put radio buttons with
onChangeevent handler - note that
nameis same for theradioinputs to form abutton group - use
useStateto store the state of visibility inAppcomponent - pass the state as prop to
Notes - in
Notesuse the prop to show the desired notes
- Create new reducer
filterReducerto store value of filter
- put the reducer function
filterReducer - create action creator
filterChange
- Modify index.js to combine the reducers
filterReducerandnoteReducer
- import
combineReducersfromredux - use
combineReducersto combine the two reducers - change
createStoreto take the combined reducer
- Change the code to use redux instead of useState
- modify
Notes.jsto now only read thenoteskey from the state - dispatch to the reducer when radio button is clicked
- modify
Notes.jsto use filter from redux store instead of useState
- Create separate component for
VisibilityFilter
- in
Appuse theVisibilityFiltercomponent to display the filter
- note that
- Using Redux Toolkit
- Install the Redux Toolkit
- npm install @reduxjs/toolkit
- Modify
index.jsto use toolkit
- import and use
configureStoreto create store, which will replacecombineReducersandcreateStore
- Modify
noteReducer.jsto use toolkit
- use
createSliceto refactor the reducer and action creators - use export using toolkit
- how is
action.typederived?
- Using Redux devtool
- Install devtool from https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd
- this only works if using Redux Toolkit
- Open the Redux devtool window
- See the change in state for all the actions
- Dispatch an action from the toolkit
TO-DO:
Part 6-c: Communicating with server in a redux application
WE-WILL-LEARN:
dispatchcan also take a function. if we pass a function todispatch, thendispatchwill execute that function by passing dispatch as the first argument
LECTURE-VIDEO:
- Getting notes from the backend while using Redux
- Create
db.jsonfile in root folder, and put some data - Install and run json server
- npm install json-server --save-dev
- in
package.jsonadd script to run json-server - npm run server
- Create service to fetch data from backend
- npm install axios
- create file
services/notes.jsto fetch data using axios from the backend
- Get initial data from backend
- in
noteReducerchange initial state to empty array - in
noteReduceradd an action to append a single note - in
noteReduceradd an action to set all the notes - in
App, create auseEffectto load the initial data from json-server
- Create
- Storing a note to the backend while using Redux
- When creating note, add functionality to also add the new note to backend
- in
services/notes.js, add function to create note in backend - in
NoteFormcomponent, modify to call the service to backend
- You can also change the
toggle importancefunctionality to also update backend
- Using thunk to move server connection logic to Redux action creator
- Modify the initial notes loading functionality to move server communication to a
thunk
- change
AppuseEffect to just dispatching return value ofinitializeNotesaction creator - in
noteReducer, create a new thunkinitializeNotesthat will:- get all notes from the server
- then dispatch the notes to Redux store
- Modify the
createNotefunctionality to move server communication to athunk
- change
NoteFormcomponent back to just dispatching return value ofcreateNoteaction creator - in
noteReducer, replace thecreateNoteaction creator withcreateNotethunk that will:- call RestAPI to create a note in the server
- then dispatch the new note to Redux store
- Refactor Redux store creation
- create
store.jsin root folder - move all Redux store creation code from
index.jstostore.js - in
index.jsimport the store to pass toApp
- Modify the initial notes loading functionality to move server communication to a
TO-DO:
Part 6-d: React Query, useReducer and the context
WE-WILL-LEARN:
LECTURE-VIDEO:
- Using React Query
- Setup for using React Query
npm install @tanstack/react-query- setup
index.jsxto use React Query
- Retrieve the notes in the
Appcomponent
- start json-server
- use
useQuery
- Refactor the server call to it's own
requestsfile
- Adding new note via React Query
- Add function
createNoteinrequeststo post note to server - In
AppadduseMutationcode for adding new note
- call the
mutatemethod to post the new note to server via React Query - invalidate the
notesresult, so that React Query re-fetches the notes
- Add functionality to toggle importance
- add toggle function to call server in
requests - add mutation code in
Appwhen note is toggled
- Add function
- Optimize React Query code to minimize calls to server
- Modify
onSuccessof new note creation to manually add new note to React Query state
- add
newNoteparameter to callback function ofonSuccess - getQueryData for
notes(queryKey has to be an array) - manually setQueryData for
notesadding thenewNote
- Turn off
refetchOnWindowFocuswhere not required - React Query is server-state library, Redux is a client-state library
- Modify
- useReducer and context
- Implement
useReducerfor a simple counter app
- create a new
CounterAppfile - render
CounterAppfromindex.jsx
- Use
contextto pass the state
- create a new file
CounterContext, and usecreateContextto create a context for the counter state - in
CounterAppuse the context provider fromCounterContextto provide the counter state toCounterApp - use
useContextofCounterContextto read the value provided by the context provider in all required components
- Refactor the code
- move all counter reducer related code to
CounterContext.jsxfile - in
index.jsx, wrap theCounterAppwithCounterContextProvider - refactor
useContextcode intoCounterContextto fetch the state and the dispatch separately - note that
useCounterValueanduseCounterDispatchare custom hooks
- Implement
- Choosing the correct state management solution
TO-DO:
Part 7-a: React-router
WE-WILL-LEARN:
- How to use react router for client side routes
LECTURE-VIDEO:
- Implement react router
- Create new project
react-routerusing react-start-kit - Create simple SPA with menu
- Install react router
- Write code to use simple react router
- use
BrowserRouterto encapsulate all code that will use router - create
Linkfor the required links - use
RoutesandRouteto handle the links to components
- Create new project
- Add parameterized route and useParams to read the parameter
- Show notes
- create a
Notes.jsxcomponent to show all notes - hardcode
notesarray with some notes inApp.jsxand pass as props toNotes.jsx
- Create parameterized route for
note/:idwithuseParams
- update
Notescomponent that createsLinkto individual notes based on id - create a new
Routeto handlenote/:idroute - create
Notecomponent withuseParamsthat will display individual note
- Add useNavigate to programmatically navigate to a url
- Create
loginroute
- create a
useruseState to keep track of logged in user - if no
userstate, then show link tologinthat is a link tologinroute
- Create
Logincomponent - Create a new route in
App.jsxto direct theloginpath toLogincomponent - Modify
Logincomponent to handle user login
- pass the
setUserfromApptoLogin - create a login form that takes a username and sets it to
userstate - use
useNavigateto navigate to/route after logging in - in
App.jsxuseNavigatecomponent inusersroute to conditionally redirect tologinroute when not logged in
- Create
- Use
useMatchfor better parameterized route- Move
BrowserRoutercomponent toindex - In
App, useuseMatchto get thenotes/:idparameter to find the note - Pass and read the single
noteobject to theNotecomponent
- Move
- Build and deploy the react router app to node server
TO-DO:
Part 7-b: Custom hooks
WE-WILL-LEARN:
- Creating and using custom hooks
- Custom hooks follow the same rules as react hooks
- Create custom hooks when you need complicated logic with react hooks that can be re-used
LECTURE-VIDEO:
- Create custom hook for counter application
- Create new project using react-start-kit
- Create simple counter application
- Move counter logic to custom hook
useCounterinhooksfolder
- make sure the name of custom hook always starts with
use
- Use
useCounterfor two different counters in the sameAppcomponent
- Using custom hook for forms
- Create an App with forms
- Create
useFieldcustom hook inhooksfolder - Use it in a form field
- Use the spread operator
- to pass props to component from object with same named keys
- to pass attributes to elements from object with same named keys
TO-DO:
Part 7-c: More about styles
WE-WILL-LEARN:
- use of readymade UI libraries for styling in react
LECTURE-VIDEO:
- React Bootstrap
- npm install react-bootstrap
- add a link for loading the CSS stylesheet for Bootstrap inside of the head tag in the
public/index.htmlfile - container
<div className="container"> - import
{ Table,Form,Button }from 'react-bootstrap' - render list of notes as a table
- improve the form in the Login view with the help of Bootstrap
Form - stlye notification message using
Alertcomponent - navigation structure
- Material UI
- install the library with command
{npm install @mui/material @emotion/react @emotion/styled} - use
Material UIto make the same changes that were made usingreact-bootstrap - each component has to be imported separately
- install the library with command
- Other styling options
npm install styled-components
TO-DO:
- The exercises related to the topics presented here can be found at the end of this course material section in the exercise set for extending the blog list application.
Part 13-a: Using relational databases with Sequelize
WE-WILL-LEARN:
- Using Relational Database (Postgres) instead of mongoDB for the notes app
- Using
SequelizeORM to create JS Model for Postgres tables
USEFUL-SQL-COMMANDS:
- Create a table called
notes
CREATE TABLE notes (
id SERIAL PRIMARY KEY,
content text NOT NULL,
important boolean,
date time
);
- Insert a row into
notestable
insert into notes (content, important) values ('Relational databases rule the world', true);
- Read rows from
notestable
select * from notes;
- Remove the table
notes
drop table notes;
LECTURE-VIDEO:
- Setup the relational database
- Create a Postgres DB in render
- Install
Postgresqlextension by Chris Kolkman to connect to the database
- make sure to correctly setup ssl
- Use the
Postgresqlextension
- create table
notesin the database using the extension - insert a few rows into the table
- check the table and the inserted rows with the extension
- Create notes-server to connect to relational database
- Create
part13/notes-server
- make the folder
notes-server npm init -yto create a new npm projectnpm install express dotenv pg sequelize
- Add the DB connection string to
.envfile - Write api to read all notes from db
- create
index.js - write the
api/notesforgetmethod to read all notes from DB usingselectquery
- Create
- Using Sequelize ORM
- Use
SequelizeORM to rewriteapi/notesgetmethod
- create a
ModelforNote - use the
Notemodel to get all notes from DB
- Add api to create a single note with simple error handling
- remember to use middleware to correctly read json data sent to server
- note the difference between
createandbuild
- Call the single note creation api
- Use
- Additional notes on relational database
- Creating database tables automatically using the
Sequelizemodel - Add api to read a single note
- Add api to modify a note
- Printing the objects returned by
Sequelizeto the console
- using
toJSON - using
JSON.stringify
- Creating database tables automatically using the
TO-DO:
Part 13-b: Join tables and queries
WE-WILL-LEARN:
- User management in sequelize
- Joining data from two tables in sequelize
- Using query parameters in REST API
LECTURE-VIDEO:
- Refactoring the code
- Start with
utildirectory
- create
config.jsto read configurations from environment - create
db.jsto put DB boilerplate code
- Refactor route handling to controllers
- move notes routes to
controllers/notes.js - modify
index.jsto use the notes controller for all/api/notesrequests
- Refactor models
- move
Notemodel tomodels/note.js - create
models/index.jsto centralize all models related code
- Move db connection code to
util/db.jsfile
- add a function to test the db connection, then call it before starting app
- Refactor notes controller
- put repetitive code to middleware
- call middleware in the routes where required
- Start with
- Create user login
- Create
Usermodel
- create
models/user.jsforUsermodel - include the
Usermodel tomodels/index.js
- Create routes for user management
- create
controllers/users.js - create
controllers/login.js - add
SECRETin.envandutil/config.js - npm install jsonwebtoken
- include the routes in
index.js
- Use
REST Clientto test login
- create user from the
REST Client - login with user and get token
- Create
- Add user to note
- Add relation between
UserandNote
- in
models/index.jsadd the foreign key fromUsertoNote
- Modify note post route to also insert logged in userId
- in
controllers/notes.js, addtokenExtractormiddleware - use
tokenExtractorto decode the the userId from token - include the userId while inserting a new note into the DB
- Use
REST Clientto create note using token
- call api to create note by passing token and note object
- Add relation between
- Cleaning up & more queries
- Modify get notes to return the full user
- Modify get users to return notes created by user
- Modify get notes to use query parameters to fine tune sequelize query
- include the
importantfield in the api call - include the option to show all results when
importantis not specified in the query - modify
Notemodel to not allow null forimportant - include searching text inside
contentfield - exclude
whereclause when not required
TO-DO:
Part 13-c: Migrations, many-to-many relationships
WE-WILL-LEARN:
- How to properly migrate database (create / update database schema)
- Through table, and many-to-many relations
- Other sequelize functionalities
LECTURE-VIDEO:
- Data migration the proper way
- Create the initial data migration script
- create the file
migrations/20230929_00_initialize_notes_and_users.js
- Write code to run migrations
- npm install umzug
- add migration code to
util/db.js - remove the
synccode frommodels/index.js
- Run the first migration
- drop all existing tables
- start the node server
- Admin user and user disabling
- Create a user
- Create a few notes
- Stop the server
- Create migration file with new fields
- create the file
migrations/20230929_01_admin_user_disabling.js
- Make corresponding changes to
models/User - Start the server
- Make changes to loginRouter to disallow logging in for users with
disabledfield set to true - Try logging in with disabled user
- create a disabled user
- using REST Client, try logging in with the disabled user vs. a not-disabled user
- Create route for admin user to change status of a user
- make changes in
controllers/users - move
tokenExtractorfromcontrollers/notestoutil/middleware.js - import
tokenExtractorin bothcontrollers/usersandcontrollers/notes
- Create admin user that can change disabled user to enabled user
- convert user 1 to admin
update users set admin=true where id=1;
- login with user 1
- use token from user 1 to update user 2 to enabled
- Create rollback functionality to undo migration
- modify
util/dbto put rollback function - create
util/rollback.jsthat calls the rollback function - add script in package.json to run the rollback file
- Creating many-to-many relationship to allow users to be part of multiple teams
- Create migration for
teamsandmembershipstables in filemigrations/20230929_02_add_teams_and_memberships.js - Make models form
TeamandMembershipto reflect updated schema - Update
models/indexto define the new relationships - Create some teams
- Add users to teams in the membership table
- Now change get users route to include teams of the users, and excluding through table data
- Understand the object returned by sequelize such as in the code below:
const user = await User.findByPk(1, { include: { model: Note, }, }); user.notes.forEach((note) => { console.log(note.content); });
- Creating new return object when required shape is different from object returned by database
- Create migration for
- Revisiting many-to-many relationships
- Create migration for
user_notesthrough table to associate many users with many notes - Create
userNotemodel - Modify
models/indexfor the many to many relation - Modify get users route to return users' notes and user marked notes
- In the route, add the author of the note
- Create migration for
- Concluding remarks
- Lazy fetch based on query parameter
- Modify get user so that it fetches the user's teams only if the query parameter
teamsis set in the request- note how
getTeamsmethod is generated by sequelize based on defined relationships
- note how
- Defining default scopes for model
- in
Usermodel, exclude disabled users by default - also define
adminscope - includ admin scope in sequelize query with
User.scope('admin').findAll()
- Adding methods to sequelize models
- add instance method
numberOfNotes - add class method
with_notes
- Repeatability of models and migrations
- generating migration and model using sequelize command line tool
TO-DO:
Part 9-a: Background and introduction
WE-WILL-LEARN:
LECTURE-VIDEO:
- Main principles
- Typed superset over JavaScript
- Compiles to plain JavaScript
- Type annotations
- input type of function
- output type of function
- Typing
- explicit typing
- inferred type
- Lifecycle of TypeScript
- Why to use TypeScript
- type checking
- static code analysis
- code level documentation
- What TypeScript does not do
- external libraries with no types
- runtime validations
TO-DO:
Nothing to do!
Part 9-b: First steps with TypeScript
WE-WILL-LEARN:
LECTURE-VIDEO:
- Setting up TypeScript tooling for Node project
- Manual setup
- create folder
- create npm project
npm init -y - install libraries
npm install --save-dev ts-node typescript - add script inside package.json
- Create our first TypeScript function, with plain JavaScript
- create a file
multiplier.ts - run it with
npm run ts-node -- multiplier.ts
- Let's use TypeScript
- give input types for function parameters
- Create custom type
- create union type
- see type suggestion
- try using a not accepted argument
- Add return type
- Throw error when required
- catch the error without putting any type
- catch the error as
unknown- then requires Type narrowing to access error.message
- Putting TypeScript in Express server
- Change
tsconfig.json - Configure script
- Create
index.tsfile - Run it
- change config to make it work
- change back config
- Types for express
- convert
requiretoimport - add required types to make code work
npm install --save-dev @types/express
- Other config related errors
- Install ts-node-dev
npm install --save-dev ts-node-dev- add package.json script
- Change
- Add api endpoint for calculate
- Add code for
/calculateendpoint
- export and import the
multiplicatorfunction - use it in the api
- Difference between explicit and implicit
any - Disallow explicit
any
npm install --save-dev eslint @typescript-eslint/eslint-plugin @typescript-eslint/parser- configure
.eslintrc - add script to
package.json
- Setup more eslint rules
- modify
.eslintrc - fix the unsafe
anyissues - do some code level validation to sanitize data
- Using type assertion
- import type
Operation - use it to assert the
opthat is posted tocalculateapi - we can now remove the eslint warning, but this could cause runtime errors
- Add code for
TO-DO:
Part 9-c: Typing an Express app
WE-WILL-LEARN:
LECTURE-VIDEO:
- Setting up production grade project
- Initialize a project using TypeScript
- create directory
flight_diary cd flight_diarynpm init -ynpm install typescript --save-dev- add script run typescript
- create tsconfig.json by initializing typescript
- Customize to our needs
- configure tsconfig for our needs
- install required additional packages
- create
.eslintrc - install and configure ts-node-dev for dev
- Get a basic node server working
- Create
index.ts - Build the project
- Eslint ignore the js files
- Create and run script from the compiled
buildfolder
- Start coding
- Create a directory structure
- Make a new
diariesroute
- create
diaries.tsinsideroutesfolder - call it from index.ts
- Serve the data
- store the data in
data/entries.json - create
src/services/diaryService.tsto manipulate the data - configure
resolveJsonModulein tsconfig
- Create and use
types
- create file
src/types.ts - add
Weather,Visibilitytypes - add
DiaryEntryinterface - use our type in diaryService
- do type assertion for diaryData
- Convert the json data directly to typed data
- create file
data/entries.tsand export typed data - use typed data directly into service
- Defining optional fields in types
- Utility types
- using
PickandOmitto choose or remove from object types - create
NonSensitiveDiaryEntryfromDiaryEntrytype by omittingcommentfield - use
NonSensitiveDiaryEntryfor the servicegetNonSensitiveEntries- there is an issue!
- modify to manually remove sensitive data
- Complete the routes
- Adding functionalities
- Fetching one specific diary entry
- Add api for specific id
- fix the problem for potential
undefinedvalue
- fix the problem for potential
- Adding a new diary
- convert argument to object
- use utility type to define new diary entry without the
idfield - ignore error in post
- parse json object in api
- Convert the incoming post data to typed data
- define function
toNewDiaryEntryinutils.ts - use it in the api
- remove the ignore error from routes/diaries.ts
- set the object param to
unknown
- Check the incoming fields using
type guard
- check comment field
- check date field
- weather
- use Enum
- also make changes to
data/entries.ts - need to assert the
NewDiaryEntryasDiaryEntry
- visibility
- use Enum
- Complete the
toNewDiaryEntryfunction
- we can remove the existence check because of the
inoperator
- Test the new diary entry api
TO-DO:
Part 9-d: React with types
WE-WILL-LEARN:
- TypeScript will help us catch the following errors:
- trying to pass an extra/unwanted prop to a component
- forgetting to pass a required prop to a component
- passing a prop with the wrong type to a component
- Useful resources
LECTURE-VIDEO:
- Create React App with TypeScript
- Create
my-appwithcreate-react-appusing TypeScript template
npx create-react-app my-app --template typescript
- Modify default app
- in
tsconfig.json, turn offallowJs - create
.eslintrc - add script to run linting
- Examining a React component
- delete all files inside
srcfolder - delete all except
index.htmlinpublicfolder - create
index.tsxinsrcfolder - types are defined for component as in a regular function
- we can remove the return type in component
- type assertion for
document.getElementById
- Create
- Deeper type usage
- Create types
- create file
types.tsinsrcfolder - add the required interfaces
- create a union type
- refactor the types
- create a base
extendthe base
- Use types in
App.tsx
- create
App.tsx- import types
- return null for now
- export App
- play around with
kind- TypeScript implicitly narrows the type based on the
kind
- TypeScript implicitly narrows the type based on the
- Discriminated union - type narrowing of union type based on literal attribute
- write jsx to show CourseParts
- show name and exerciseCount
- use ternary for type narrowing
- Exhaustive type checking to make sure all union types have been used
- React app with state
- Create a new App-note.tsx
- the first
useStateinfers the typestring - the second
useStateis not able to infer the type- lets explicitly put the type
- Add code to initialize some data, and show it in jsx
- Add function to create new note
- put a form with controlled input
- add
onSubmitfor the form- fix the type error for event
- Communicating with the server
- start the json server from part6/redux-notes
- install axios
- get the initial notes from rest api
- set the generics type for
axios.getmethod
- Connect the add note to server
- A note about defining object types
- interface vs type
TO-DO:
Part 9-e: Grande finale: Patientor
WE-WILL-LEARN:
LECTURE-VIDEO:
- Working with an existing project
- Clone and run an existing frontend project
https://github.com/fullstack-hy2020/patientor.git- cd patientor
- rm -rf .git
- npm i
- npm run dev
- Run existing backend project
- cd patientor-backend
- npm run dev
- Serve the expanded patients data
- copy the expanded data set
- create the required types for entry
- start with a common type, refer to
codefromDiagnosetype fordiagnosisCodes - extend it
- creat a union type
- understanding and using
UnionOmit
- start with a common type, refer to
TO-DO:
Part 11-a: Introduction to CI/CD
WE-WILL-LEARN:
- Software development life cycle
- Meaning of CI / CD
LECTURE-VIDEO:
- Software journey from development to production
- Follow the GitHub flow for software development
- create a feature branch
- complete coding and testing the feature
- create a
Pull requestto the main branch- another user checks the code, and merges to the main branch
- Build the application to make it ready for
deployingto server Deploythe code to production
- CI / CD
- CI:
Continuous Integrationrefers to merging developer changes to the main branch. This involves:
mergingfeature branch to main branchlintbuild: e.g. build the frontend code and put it in static folder of backend repositorytest: run all frontend/backend jest, cypress tests to make sure they passpackage: e.g. create a zip file of part3/notes-server withALLrequired code files includingnode_modulesdeploy: e.g. ftp the zip file to production server, unzip it, and restart the node server
- CD: could mean
Continuous DeliveryorContinuous Deployment
- the practice where the main branch is kept deployable at all times
- automated deployments triggered from merges into the main branch
Important principals: The goal is better, faster software development with fewer preventable bugs and better team cooperation
- How to make sure that tests run on all code that will be deployed?
- How to make sure that the main branch is deployable at all times?
- How to ensure that builds will be consistent and will always work on the platform it'd be deploying to?
- How to make sure that the changes don't overwrite each other?
- How to make deployments happen at the click of a button or automatically when one merges to the main branch?
- Types of CI Setup
- Self hosted setup, e.g. Jenkins
- Cloud based solution, e.g. GitHub Actions
- CI:
TO-DO:
Part 11-b: Getting started with GitHub Actions
WE-WILL-LEARN:
- How to create GitHub Action
LECTURE-VIDEO:
- Creating a GitHub Action
- be aware of the basic needs required to create CI operation
- create the folder
.github/workflowsin the root of the repository - create a
workflowfilehello.ymlinside.github/workflowsdirectory - put the workflow code in YAML with the following elements:
- name: identifier for the workflow
- on: the event that will trigger the workflow to be executed
- jobs: one or more
jobs(series of steps) to be executed in the workflow
- commit & push
- check the
Actionstab in GiHub to see the workflow - add step to list the files
- you can configure a GitHub action workflow to start once:
- An event on GitHub occurs such as when someone pushes a commit to a repository or when an issue or pull request is created
- A scheduled event, that is specified using the cron-syntax, happens
- An external event occurs, for example, a command is performed in an external application such as Slack or Discord messaging app
- Setting up lint, test and build steps
- create new workflow file
pipeline.yml - add new workflow code
- create a job
- setup environment to run the job
- add step to checkout the code
- add step to setup node
- add step to run npm install (you can set working-directory if required)
- add step to run linting (you can set working-directory if required)
- commit and push code
- create new workflow file
TO-DO:
Part 11-c: Deployment
WE-WILL-LEARN:
- deployment
LECTURE-VIDEO:
- How to deploy in CI / CD
- good rules for CI / CD
- expect anything that can go wrong to go wrong
- silent failures are very bad
- read what a good deployment system should do
TO-DO:
Part 11-d: Keeping green
WE-WILL-LEARN:
- Put conditions for executing Github Actions
- Versioning git repositories
LECTURE-VIDEO:
- Workflow in Pull Requests
- keep the main branch green
- usage of pull requests
- for code review
- automate trigger of tasks in the CI pipeline
- configure GitHub action to trigger on PR
- NOTE!! make sure your
PRis from your feature branch to YOUR main branch
- put condition on
deploystep to only execute on certaingithub.event_namecondition
- About versioning
- types of versioning
- semantic versioning: {major}.{minor}.{patch} (e.g. 1.2.23)
- hash versioning: is the hash from the commit point
- ways to keep track of versions
- something in the code itself (e.g. a file in the repo with version number)
- something in the repo or repo metadata (e.g.
tagsorreleasesin git) - something completely outside the repo (e.g. a spreadsheet that lists the Semantic Version and the commit it points to)
- best versioning workflow:
- CI system keeps track of development by hash versioning
- once code is successfully merged to main branch, then CI system gives a semantic version
- use anothrNick/github-tag-action for automating semantic versioning in GitHub Actions
- put it in a separate job
- use the
needskeyword to make this job depend on the previous job,simple_deployment_pipeline - only run this job on PR merge
- checkout the code in the first step
- in the tag step
- change the default bump to
patch - add token for authentication in your repository, as the action is third-party which needs authentication
- only run the step if the commit message does not contain
#skip
- change the default bump to
- Final notes
- when using third party actions, e.g. github-tag-action, it might be a good idea to specify the used version with hash instead of using a version number
- keep the main branch protected
TO-DO:
Part 11-e: Expanding Further
WE-WILL-LEARN:
LECTURE-VIDEO:
- Additional usage of CI pipelines
- use commit comments to automate bug tracking systems
- automate communications based on CI status
- steps to build discord success/failure notification to discord
- use GitHub action discord-webhook-notify to send notification to University of Helsinki discord channel - fullstack_webhook using their webhook
- a success indication if a new version gets deployed
- an error indication if a build fails
- regarding metrics - keep metrics of build time so that you can
- project future build times
- see if there are any sudden build time changes
- periodic tasks - automate with either commonly available tools, or, if not available, build automation yourself. you can schedule through GitHub Actions
- building vs buying - it's almost always better to use a tool that already does the job than to roll your own solution
- creating your own pipeline
- create a new repo
- includ phonebook server code into
serverfolder - put phonebook frontend into
clientfolder - make sure that
MONGODB_URIenv is entered into herokuconfig vars - make sure
HEROKU_API_KEYis entered as github actions secret - make sure that the build script in
package.jsonis namedbuild- it should build to a folder that is referred from the backend's
express.static
- it should build to a folder that is referred from the backend's
- protect your
mainbranch
- require a pull request. make sure approval is required
- do not allow bypassing even for administrators
TO-DO:
The senior phase will focus on alrogithms and projects
Day 1
WE-WILL-LEARN:
- Getting started with e-commerce project
WEB APP DESIGN
- requirements, user stories
- As a
<persona>, I want to<do something>so that<reason>- example: as a
phonebook owner, I want to see all of my contacts, so that I can see their phone number
- example: as a
- implementation detail
- How you will actually fulfill a user story
- Split each story into specific, bite-size tasks (implementation detail)
- Should always serve a user story
- examples:
- an Express route on the backend
(GET
/api/contacts) should serve up the name, phone number of all the contacts from our Postgres database. access should be restricted to only the owner of the contact. - a React component on the frontend should call
/api/contactson first load, save it on redux, and use contacts from redux to display them on the screen
- an Express route on the backend
(GET
- models
- list all the different models you will need, e.g.
users,contacts - write down all the fields for the models
- draw connections between models, e.g. one-to-one, one-to-many etc
- wireframes
- rough mockups for all the frontend screens
TO-DO:
- gather user stories
- models
- implementation details
- wireframes
Day 2
WE-WILL-LEARN:
- Algorithms
- Linear search
- Binary search
- Two crystal ball
- Bubble sort
- REACTO
- Big O
- System design an ATM Machine
INTERVIEWING USING REACTO
- Read
- ask clarifying questions
- confirm your assumptions with the interviewer
- Example
- write down example
- one simple case
- edge cases (many, if you can think of)
- for arrays, always ask
- is it sorted
- can there be duplicates
- write down example
- Analyze
- tell your interviewer that you are going to analyze the simpe case first. think aloud to the interviewer as you are analyzing the simple case solution. let her hear all your thoughts; don't just analyze in your mind. come out with the solution that you are able to think of right away, don't try to be clever to come up with the optimal solution in the first go. it is ok if it is a naive solution
- talk about the Big(O) of the solution
- time complexity
- space complexity
- Code it completely
- Test it using the simple example
- then test for some of the edge cases, and see if you need to modify code for it
- Optimize solution if possible. but since you have done naive solution already, there isn't pressure to complete it
BIG O NOTATION
Big O is a way to categorize your algorithm's time or memory requirements based on input. It is not meant to be an exact measurement. It will not tell you how many CPU cycles it takes, instead it is meant to generalize the growth of your algorithm.
- Big O of time
- O(N) - if there is one loop through the input
- O(N^2) - if there is nested loop through the input
- O(log N) - if the input is halved in each step
- O(N log N) - if the input is halved in each step
- Big O of space
- how much memory is the algorithm going to consume?
- generally, not much focus will be put on this
- it only comes into focus when using hashing function
- look for loops!
- growth is with respect to the input
- constants are dropped
- worst case is usually the way we measure
TO-DO:
- REACTO interview problems
- Web app design for Blogslist