# WEEK 6 - AXIOS, Rest API INTEGRATION
###### tags: `WEB` `JS` `DIGITALIZE` `FRONTEND` `REACTJS`
## INTRODUCTION
In the first 4 weeks of this bootcamp we learned how to design and build backend part Rest API to be used by any client want to integrate.
After that we talked about Front-end and SPA using ReactJS, but we didn't integrate Front-end with Rest API.
To integrate front-end with back-end Rest API we have to make http requests.
Today we will learn how to integrate front-end with backend Rest API using axios library, so we will implement CRUD operations.
## AXIOS
Axios is a Javascript library used to make HTTP requests from node.js or XMLHttpRequests from the browser and it supports the Promise API that is native to JS ES6. It can be used intercept HTTP requests and responses and enables client-side protection against XSRF. It also has the ability to cancel requests.
**Why axios?**
* Make XMLHttpRequests from the browser that support ES6.
* Make http requests from node.js
* Supports the Promise API.
* Intercept request and response
* Transform request and response data
* Cancel requests
* Automatic transforms for JSON data
* Client side support for protecting against XSRF
### INSTALLATION
`npm i axios`
Syntax:
```
// send a POST request
axios({
method: 'post',
url: 'endpoint',
data: {
firstName: 'Finn',
lastName: 'Williams'
}
});
```
Shorthand methods for Axios HTTP requests
* axios.request(config)
* axios.get(url[, config])
* axios.delete(url[, config])
* axios.head(url[, config])
* axios.options(url[, config])
* axios.post(url[, data[, config]])
* axios.put(url[, data[, config]])
* axios.patch(url[, data[, config]])
For example, we can use `.post` method instead as below:
```
axios.post('/login', {
firstName: 'Finn',
lastName: 'Williams'
});
```
Axios returns a promise that is either fulfilled or rejected, depending on the response from the backend service.
```
axios.post('/login', {
firstName: 'Finn',
lastName: 'Williams'
})
.then((response) => { // if success
console.log(response);
}, (error) => { // if failed
console.log(error);
});
```
Information that included in the response of axios request:
```
{
// `data` is the response that was provided by the server
data: {},
// `status` is the HTTP status code from the server response
status: 200,
// `statusText` is the HTTP status message from the server response
statusText: 'OK',
// `headers` the headers that the server responded with
// All header names are lower cased
headers: {},
// `config` is the config that was provided to `axios` for the request
config: {},
// `request` is the request that generated this response
// It is the last ClientRequest instance in node.js (in redirects)
// and an XMLHttpRequest instance the browser
request: {}
}
```
Example: Let's see response of a request
```
axios.get('https://api.github.com/users/mapbox')
.then((response) => {
console.log(response.data);
console.log(response.status);
console.log(response.statusText);
console.log(response.headers);
console.log(response.config);
});
```
**Sending custom headers with Axios**
Sending custom headers with Axios is very straightforward. Simply pass an object containing the headers as the last argument. For example:
```
const options = {
headers: {'X-Custom-Header': 'value'}
};
axios.post('/save', { a: 10 }, options);
```
**POST JSON with Axios**
Axios automatically serializes JavaScript objects to JSON when passed to the axios.post function as the second parameter. This eliminates the need to serialize POST bodies to JSON.
Axios also sets the Content-Type header to application/json. This enables web frameworks to automatically parse the data.
If you want to send a preserialized JSON string to axios.post() as JSON, you’ll need to make sure the Content-Type header is set.
### CLINIC LOGIN IMPLEMENTATION
Let's implement LOGIN functionality, we will use repo https://github.com/computiq-training/dig-w3-security.git
for back-end Rest API.
First, clone the repo, config .env file and run MongoDB Compass, then start the app.
**Note:** Don't forget to test API via postman to make sure that backend app is working correctly.
Now in reactJS:
`Login.js`:
```
import axios from "axios";
import { useEffect } from "react";
export const Login = ()=>{
useEffect(() => {
}, []);
const login = (e)=>{
e.preventDefault();
console.log('LOGIN')
axios.post('http://127.0.0.1:5000/api/v1/auth/signin', {
username:"admin3",
password:"123!@#Aa"
})
.then((res)=>{
console.log(res)
})
.catch((e)=>{
console.log(e)
})
}
return <>
<section className="bg-[#F2F8FF] h-screen flex flex-row justify-center items-center ">
<div className="w-96 h-96 bg-[#3995FF] rounded-lg ">
<form onSubmit={login} className="h-full flex flex-col justify-evenly items-center">
<input type="text" required/>
<input type="password" required/>
<input className="py-2 px-4 hover:bg-white rounded cursor-pointer" type="submit" value="LOGIN"/>
</form>
</div>
</section>
</>
}
```
In case you face CORS issue, just enable cors from back-end (nodejs) as below:
Cors error message:
```
Access to XMLHttpRequest at 'http://127.0.0.1:5000/api/v1/auth/signin' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
```
How to Fix
```
const cors = require('cors')
...
app.use(cors())
```
Now try to submit, you will see response of http request in the console log as below:

But What about reirect user to the app and protecting privates routes?
We will implement them one by one.
## LOCAL STORAGE
The local storage API allows developers to interact with the web storage to persist data. In other words, we can save data to the client's computer, where it stays until the user clears the browser's storage.
For React developers—or Single-Page-Application devs in general—this means we can keep the user logged in, store data that has been fetched from an API, or save user settings.
We can use the API to store data in the form of key/value pairs. The functions it provides are the following 5:
```
// Get
localStorage.getItem('dark-mode');
// Set
localStorage.setItem('dark-mode', true);
// Remove
localStorage.removeItem('dark-mode');
// Remove all keys
localStorage.clear()
// Get n-th key name
localStorage.key(2);
```
## Routes Protection
Our SPA may have several routes as following:
- Auth Routes like (Login, signup), these routes should be accessible via Guests and authorized users.
- Guests Routes like(Privacy policy, About, Plans)
- Protected Routes like(Profile, Patients, History, etc)
So we need to make some of them available for guests users, and some of them should be accessible by authorized users only.
Hints:
- We need to store JWT Token in local storage once login is success.
- Redirect user to patients page once the login is success and there is a token stored in local storage.
- We have to wrap our application with AuthProvider context.
**Implement AuthProvider**
```
import axios from "axios";
import { createContext, useState, useEffect } from "react";
import { API_VERSION, BASE_URL } from "../constants/web_service";
const initState = {
isAuth:false,
isInit:false,
user:null,
login: ()=>Promise.resolve(),
logout:()=>{}
}
export const AuthContext = createContext(initState)
export const AuthProvider = ({children})=>{
const [state, setstate] = useState(initState);
useEffect(() => {
const initialise = async () => {
try {
console.log('load from local storage to check')
let user = window.localStorage.getItem('userData');
if(user)
{
user = JSON.parse(user);
setstate({...state,
isAuth:true,
user:user
})
}
} catch (err) {
setstate({...state,
isAuth:false,
user:null
})
}
};
initialise();
}, []);
const setSessionData = (data) => {
if (data) {
let token = data.token;
localStorage.setItem('userData', JSON.stringify(data));
axios.defaults.headers.common.Authorization = `Bearer ${token}`;
} else {
console.log('removed')
localStorage.removeItem('user_data');
delete axios.defaults.headers.common.Authorization;
}
};
const login = (username, password)=>{
console.log('LOGIN')
console.log(BASE_URL)
const x = BASE_URL+API_VERSION;
console.log('x is ',x)
axios.post(`${BASE_URL+API_VERSION}auth/signin`, {
username:username,
password:password
})
.then((res)=>{
console.log(res)
localStorage.setItem('userData', res.data.data.token)
console.log(localStorage.getItem('userData'))
setSessionData(res.data)
// update state
setstate({
...state,
isAuth:true,
user:res.data
})
})
.catch((e)=>{
console.log(e)
})
}
const logout = ()=>{
setSessionData(null)
setstate({
...state,
isAuth:false,
user:null
})
}
return <AuthContext.Provider
value={{
...state,
login,
logout
}}
>
{children}
</AuthContext.Provider>
}
```
`Login.js`
Check for isAuth and navigate user to login route in case is is not authorized.
```
import axios from "axios";
import { useEffect, useState, useContext } from "react";
import { Navigate } from "react-router-dom";
import { AuthContext } from "../contexts/AuthContext";
export const Login = ()=>{
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const context = useContext(AuthContext);
console.log('c in login is ',context)
const {login, isAuth} = context;
console.log('is Auth',isAuth)
useEffect(() => {
}, []);
const onUsernameChange = (e)=>{
setUsername(e.target.value)
}
const onPasswordChange = (e)=>{
setPassword(e.target.value)
}
const submit = (e)=>{
e.preventDefault()
login(username,password)
}
return <>
{
isAuth && <Navigate to="/patients"/>
}
<section className="bg-[#F2F8FF] h-screen flex flex-row justify-center items-center ">
<div className="w-96 h-96 bg-[#3995FF] rounded-lg ">
<form onSubmit={submit} className="h-full flex flex-col justify-evenly items-center">
<input value={username} onChange={onUsernameChange} type="text" required/>
<input value={password} onChange={onPasswordChange} type="password" required/>
<input className="py-2 px-4 hover:bg-white rounded cursor-pointer" type="submit" value="LOGIN"/>
</form>
</div>
</section>
</>
}
```
logout in `layout.js`
```
import { NavLink, Outlet } from "react-router-dom";
import ThemeContext from "../contexts/ThemeContext";
import { useContext } from "react";
import { Sidebar } from "../components/sidebar";
import { Switcher } from "../components/Switcher";
import { AuthContext } from "../contexts/AuthContext";
export default function Layout(){
const context = useContext(ThemeContext);
const authContext = useContext(AuthContext)
const {logout}=authContext;
console.log('c',context)
console.log('lgout is',logout)
const color = context.settings.themeValue.background;
return <>
<header className={`h-16 ${color}`}>
<Switcher/>
<button onClick={logout}>Logout</button>
</header>
<div className="flex flex-row">
<Sidebar/>
<div id="detiails" className="w-full p-8">
<Outlet/>
</div>
</div>
</>
}
```
Protecting `patients` route:
```
const authContext = useContext(AuthContext);
const {isAuth} = authContext;
console.log('is auth in patients',isAuth)
...
if(!isAuth)
return <Navigate to='/login'/>
....
```
## CRUD Operations
We will implement CRUD operations for patients page, integrates our front-end app with clinic-api to accomplish that.
We will use this repo 'clini-api':
https://github.com/computiq-training/clinic-api.git
**Read patients list from API**
Since patients information is not global state, which mean we need to manage patients state inside patients page, so we don't need to share this state between the components, that's result in manage patients state inside patient page only (localy).
- Set patients default value to be empty array
`const [patients, setPatients] = useState([])`
- fetch data from api using axios inside `useEffect`
before doing this, you need to get token during login process and put it as global state in AuthContext so you can access and us it in any future requests to the server:
Define jwtToken state:
`const [jwtToken, setJwtToken] = useState('')`
`AuthContext.js`
let's set jwtToken global state once the login success
```
const loginFun = (username, password)=>{
axios.post('http://127.0.0.1:5000/api/v1/auth/signin',{
username:username,
password:password
})
.then((res)=>{
console.log(res)
localStorage.setItem('userData',JSON.stringify(res.data.data))
setIsUserAuth(true);
setUserData(res.data.data)
setJwtToken(res.data.token)
console.log(res.data)
})
.catch((e=>{
console.log('Error')
console.error(e)
}))
}
```
Also we need to pass JWTToken by value of the provider:
```
return <AuthContext.Provider
value={{
isAuth:isUserAuth,
user:userData,
login:loginFun,
logout:logoutFun,
jwtToken: jwtToken
}}>
{children}
</AuthContext.Provider>
```
Also we need to validate token which is stored in local storage:
```
useEffect(() => {
const data = JSON.parse(localStorage.getItem('userData'))
if(data)
{
if(data.token)
{
setIsUserAuth(true)
setUserData(data)
setJwtToken(data.token)
}
}
}, []);
```
Now let's use jwtToken from patients component:
```
const {user,isAuth,jwtToken} = authContext;
....
useEffect(() => {
axios.get('http://127.0.0.1:5000/api/v1/patients',{
headers:{
'Authorization':`Bearer ${jwtToken}`
}
})
.then((r)=>{
setPatients(r.data.data)
setFilteredPatients(r.data.data)
})
.catch((e=>{
console.log('error occured')
console.error(e)
}))
// const isAuth = localStorage.getItem('userData')
// console.log('user data in p ',isAuth)
}, []);
```
**Create patient**
```
const addNew = ()=>{
if(!fullName || !phone || !birthDate || !gender)
{
// alert('Please fill all the info')
open('Please fill all the info')
return;
}
axios.post(`http://127.0.0.1:5000/api/v1/patients`,{
full_name:fullName,
birth_date:birthDate,
phone:phone,
code:"801",
gender:gender
}
,
{
headers:{
'Authorization':`Bearer ${jwtToken}`
}
}
)
.then((r)=>{
console.log('response is :',r)
let pTemp = !patients?[]:patients
pTemp.push({
id:4,
full_name:fullName,
birth_date:birthDate,
gender:gender,
phone:phone
})
setPatients([...pTemp])
setFilteredPatients([...pTemp])
reset();
})
.catch((e)=>{
console.log('Error occured')
console.error(e)
})
}
```
**Delete a patient**
```
const deleteRow = (id)=>{
axios.delete(`http://127.0.0.1:5000/api/v1/patients/${id}`,{
headers:{
'Authorization':`Bearer ${jwtToken}`
}
})
.then((r)=>{
let temp = patients;
console.log(id,'deleted')
let index = temp.findIndex((item)=>{
return item.id === id
})
console.log('index,',index)
console.log('length before',temp.length)
temp.splice(index,1)
console.log('length after',temp.length)
setPatients([...temp])
setFilteredPatients([...temp])
})
.catch((e)=>{
console.log(e)
})
}
```
*Don't forget:*
Change patient object properties anywhere in the code to match object schema returned from the server, ex: id=>_id
## Code Enhancment
So far, we have implemented CRUD operations for patients page, but there are issues in the code design and non-professional implementation, let's enhance some of them:
- Define BASE URL to be constant and can be imported anywhere, so if we change envrironments from one to another, we can change BASE URL easily.
`constants/web_service.js`:
```
export const BASE_URL = 'http://127.0.0.1'
export const PORT = '5000';
export const API_VERSION = '/api/v1/';
export const DEV_URL = `${BASE_URL}:${PORT}${API_VERSION}`
```
now let's use this constant anywhere in the code, ex:AuthContext, Example
`AuthContext.js`
```
import {URL} from '../constants/web_service'
....
const loginFun = (username, password)=>{
axios.post(`${DEV_URL}auth/signin`,{
username:username,
password:password
})
.then((res)=>{
console.log(res)
localStorage.setItem('userData',JSON.stringify(res.data.data))
setIsUserAuth(true);
setUserData(res.data.data)
setJwtToken(res.data.token)
console.log(res.data)
})
.catch((e=>{
console.log('Error')
console.error(e)
}))
}
```
`patients.js`
```
import {URL} from '../constants/web_service'
...
useEffect(() => {
axios.get(`${URL}patients`,{
headers:{
'Authorization':`Bearer ${jwtToken}`
}
})
.then((r)=>{
setPatients(r.data.data)
setFilteredPatients(r.data.data)
})
.catch((e=>{
console.log('error occured')
console.error(e)
}))
// const isAuth = localStorage.getItem('userData')
// console.log('user data in p ',isAuth)
}, []);
...
const deleteRow = (id)=>{
axios.delete(`${URL}patients/${id}`,{
headers:{
'Authorization':`Bearer ${jwtToken}`
}
})
.then((r)=>{
let temp = patients;
console.log(id,'deleted')
let index = temp.findIndex((item)=>{
return item.id === id
})
console.log('index,',index)
console.log('length before',temp.length)
temp.splice(index,1)
console.log('length after',temp.length)
setPatients([...temp])
setFilteredPatients([...temp])
})
.catch((e)=>{
console.log(e)
})
}
```
- as you see, we have to pass jwtToken with value of the provider, in fact, there is a better and cleaner way to do instead of that, axios package provide us with ability to set default header value for each request without passing it manually in each request, so let's go to login function and set default authorization header once the login success and stop passing jwtToken for each component and request:
`AuthContext.js`:
```
const loginFun = (username, password)=>{
axios.post(`${URL}auth/signin`,{
username:username,
password:password
})
.then((res)=>{
console.log(res)
localStorage.setItem('userData',JSON.stringify(res.data.data))
setIsUserAuth(true);
setUserData(res.data.data)
//setJwtToken(res.data.token)
console.log(res.data)
axios.defaults.headers.common.Authorization = `Bearer ${res.data.token}`;
})
.catch((e=>{
console.log('Error')
console.error(e)
}))
}
```
Remove jwtToken state completely from anywhere in the project
Now the token will be sent by default by axios in each request.
## Bounes
**Seperate business login and Http services**
let create a base class called `AxiosBase`:
```
import axios from 'axios'
import {URL as HOST_NAME} from '../constants/web_service'
export class AxiosBase {
headers = {
}
constructor(url_prefix = "") {
this.url_prefix = url_prefix
}
async get(url, queryParams) {
try {
let response = await axios.get(HOST_NAME+this.url_prefix+url+this.mapQueryParams(queryParams))
return response
} catch (error) {
return error
}
}
async post(url, body, queryParams = null) {
try {
let response = await axios.post(HOST_NAME+ this.url_prefix + url + this.mapQueryParams(queryParams),body)
return response
} catch (error) {
// console.log('error in response of axios is',error)
// let res = handleResponse(error)
return error; // null or object of msg
}
}
async put(url, body, queryParams = null) {
try {
let response = await axios.put(HOST_NAME + this.url_prefix +url+this.mapQueryParams(queryParams),
JSON.stringify(body),
)
return response
} catch (error) {
console.log(error);
return error
}
}
async remove(url, queryParams = null) {
try {
let response = await axios.delete(HOST_NAME + this.url_prefix + url + this.mapQueryParams(queryParams))
return response
} catch (error) {
console.log(error)
return error
}
}
mapQueryParams(queryParams) {
return queryParams
? Object.keys(queryParams).map(function (key) {
return key + '=' + queryParams[key]
}).join('&')
: ""
}
}
```
let create a new class called `BaseService` at services directory.
And add the following code:
```
import { AxiosBase } from "./AxiosBase";
export class BaseService {
http
constructor(url_prefix = "") {
this.http = (new AxiosBase(url_prefix))
}
async getAll() {
return await this.http.get(``)
}
async get(id) {
return await this.http.get(`/${id}`)
}
async create(body) {
return await this.http.post(``, body)
}
async update(id, body) {
return await this.http.put(`/${id}`, body)
}
async delete(id) {
return await this.http.remove(`/${id}`)
}
}
```
Now define abstracted class that enable us to deal with CRUD http operation:
`services/PatientServices/PatientService.js`
```
import { BaseService } from "../AxiosX"
export class PatientService extends BaseService {
constructor(endpointName = 'patients') {
super(endpointName)
}
async all(){
return await this.http.get("")
}
async update(id,params){
return await this.http.put(`/${id}?`,{},params)
}
async delete(id){
return await this.http.remove(`/${id}`)
}
async create(data)
{
return await this.http.post(``,data)
}
}
```
Now to use high level class `PatientService` in `patient.jsx`:
fetch all patients
```
(new PatientService('patients')).all()
.then((r)=>{
console.log('esponse',r)
setPatients(r.data.data)
setFilteredPatients(r.data.data)
}).catch((e)=>{
console.error(e)
})
```
## TASK
History Page CRUD operations, integration with API.
## REFERENCES
https://www.npmjs.com/package/jwt-decode
https://www.npmjs.com/package/axios
https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage