In this tutorial, I will show you how to build a React.js CRUD Application to consume Web API, display and modify data with Router, Axios & Bootstrap.
Contents [hide]
- Overview of React.js CRUD App example with Web API
- Technology
- Project Structure
- Setup React.js Project
- Import Bootstrap to React CRUD App
- Add React Router to React CRUD App
- Add Navbar to React CRUD App
- Initialize Axios for React CRUD HTTP Client
- Create Data Service
- Create React Components
- Add CSS style for React Components
- Configure Port for React CRUD Client with Web API
- Run React CRUD App
- Source Code
- Conclusion
Overview of React.js CRUD App example with Web API
We will build a React Tutorial Application in that:
- Each Tutorial has id, title, description, published status.
- We can create, retrieve, update, delete Tutorials.
- There is a Search bar for finding Tutorials by title.
Here are screenshots of our React CRUD Application.
– Create an item:
– Retrieve all items:
– Click on Edit button to update an item:
On this Page, you can:
- change status to Published using Publish button
- delete the item using Delete button
- update the item details with Update button
– Search Tutorials by title:
This React Client consumes the following Web API:
Methods | Urls | Actions |
---|---|---|
POST | /api/tutorials | create new Tutorial |
GET | /api/tutorials | retrieve all Tutorials |
GET | /api/tutorials/:id | retrieve a Tutorial by :id |
PUT | /api/tutorials/:id | update a Tutorial by :id |
DELETE | /api/tutorials/:id | delete a Tutorial by :id |
DELETE | /api/tutorials | delete all Tutorials |
GET | /api/tutorials?title=[keyword] | find all Tutorials which title contains keyword |
Technology
- React 16
- react-router-dom 5.1.2
- axios 0.19.2
- bootstrap 4.4.1
Project Structure
I’m gonna explain it briefly.
– package.json contains 4 main modules: react
, react-router-dom
, axios
& bootstrap
.
– App
is the container that has Router
& navbar.
– There are 3 components: TutorialsList
, Tutorial
, AddTutorial
.
– http-common.js initializes axios with HTTP base Url and headers.
– TutorialDataService
has methods for sending HTTP requests to the Apis.
– .env configures port for this React CRUD App.
Setup React.js Project
Open cmd at the folder you want to save Project folder, run command:
npx create-react-app react-crud
After the process is done. We create additional folders and files like the following tree:
public
src
components
add-tutorial.component.js
tutorial.component.js
tutorials-list.component.js
services
tutorial.service.js
App.css
App.js
index.js
package.json
Import Bootstrap to React CRUD App
Run command: npm install bootstrap
.
Open src/App.js and modify the code inside it as following-
import React, { Component } from "react";
import "bootstrap/dist/css/bootstrap.min.css";
class App extends Component {
render() {
// ...
}
}
export default App;
Add React Router to React CRUD App
– Run the command: npm install react-router-dom
.
– Open src/index.js and wrap App
component by BrowserRouter
object.
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";
import App from "./App";
import * as serviceWorker from "./serviceWorker";
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById("root")
);
serviceWorker.unregister();
Open src/App.js, this App
component is the root container for our application, it will contain a navbar
, and also, a Switch
object with several Route
. Each Route
points to a React Component.
import React, { Component } from "react";
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
import "bootstrap/dist/css/bootstrap.min.css";
import "./App.css";
import AddTutorial from "./components/add-tutorial.component";
import Tutorial from "./components/tutorial.component";
import TutorialsList from "./components/tutorials-list.component";
class App extends Component {
render() {
return (
<Router>
<div>
<nav className="navbar navbar-expand navbar-dark bg-dark">
<a href="/tutorials" className="navbar-brand">
designmycodes
</a>
<div className="navbar-nav mr-auto">
<li className="nav-item">
<Link to={"/tutorials"} className="nav-link">
Tutorials
</Link>
</li>
<li className="nav-item">
<Link to={"/add"} className="nav-link">
Add
</Link>
</li>
</div>
</nav>
<div className="container mt-3">
<Switch>
<Route exact path={["/", "/tutorials"]} component={TutorialsList} />
<Route exact path="/add" component={AddTutorial} />
<Route path="/tutorials/:id" component={Tutorial} />
</Switch>
</div>
</div>
</Router>
);
}
}
export default App;
Initialize Axios for React CRUD HTTP Client
Let’s install axios with command: npm install axios
.
Under src folder, we create http-common.js file with following code:
import axios from "axios";
export default axios.create({
baseURL: "http://localhost:8080/api",
headers: {
"Content-type": "application/json"
}
});
You can change the baseURL
that depends on REST APIs url that your Server configures.
Create Data Service
In this step, we’re gonna create a service that uses axios object above to send HTTP requests.
services/tutorial.service.js
import http from "../http-common";
class TutorialDataService {
getAll() {
return http.get("/tutorials");
}
get(id) {
return http.get(`/tutorials/${id}`);
}
create(data) {
return http.post("/tutorials", data);
}
update(id, data) {
return http.put(`/tutorials/${id}`, data);
}
delete(id) {
return http.delete(`/tutorials/${id}`);
}
deleteAll() {
return http.delete(`/tutorials`);
}
findByTitle(title) {
return http.get(`/tutorials?title=${title}`);
}
}
export default new TutorialDataService();
We call axios get
, post
, put
, delete
method corresponding to HTTP Requests: GET, POST, PUT, DELETE to make CRUD Operations.
Create React Components
Now we’re gonna build 3 components corresponding to 3 Routes defined before.
Add item Component
This component has a Form to submit new Tutorial with 2 fields: title
& description
.
components/add-tutorial.component.js
import React, { Component } from "react";
import TutorialDataService from "../services/tutorial.service";
export default class AddTutorial extends Component {
constructor(props) {
super(props);
this.onChangeTitle = this.onChangeTitle.bind(this);
this.onChangeDescription = this.onChangeDescription.bind(this);
this.saveTutorial = this.saveTutorial.bind(this);
this.newTutorial = this.newTutorial.bind(this);
this.state = {
id: null,
title: "",
description: "",
published: false,
submitted: false
};
}
onChangeTitle(e) {
this.setState({
title: e.target.value
});
}
onChangeDescription(e) {
this.setState({
description: e.target.value
});
}
saveTutorial() {
var data = {
title: this.state.title,
description: this.state.description
};
TutorialDataService.create(data)
.then(response => {
this.setState({
id: response.data.id,
title: response.data.title,
description: response.data.description,
published: response.data.published,
submitted: true
});
console.log(response.data);
})
.catch(e => {
console.log(e);
});
}
newTutorial() {
this.setState({
id: null,
title: "",
description: "",
published: false,
submitted: false
});
}
render() {
// ...
}
}
First, we define the constructor and set initial state, bind this
to the different events.
Because there are 2 fields, so we create 2 functions to track the values of the input and set that state for changes. We also have a function to get value of the form (state) and send the POST request to the Web API. It calls TutorialDataService.create()
method.
For render()
method, we check the submitted
state, if it is true, we show Add button for creating new Tutorial again. Otherwise, a Form will display.
export default class AddTutorial extends Component {
// ...
render() {
return (
<div className="submit-form">
{this.state.submitted ? (
<div>
<h4>You submitted successfully!</h4>
<button className="btn btn-success" onClick={this.newTutorial}>
Add
</button>
</div>
) : (
<div>
<div className="form-group">
<label htmlFor="title">Title</label>
<input
type="text"
className="form-control"
id="title"
required
value={this.state.title}
onChange={this.onChangeTitle}
name="title"
/>
</div>
<div className="form-group">
<label htmlFor="description">Description</label>
<input
type="text"
className="form-control"
id="description"
required
value={this.state.description}
onChange={this.onChangeDescription}
name="description"
/>
</div>
<button onClick={this.saveTutorial} className="btn btn-success">
Submit
</button>
</div>
)}
</div>
);
}
}
List of items Component
This component has:
- a search bar for finding Tutorials by title.
- a tutorials array displayed as a list on the left.
- a selected Tutorial which is shown on the right.
So we will have following state:
searchTitle
tutorials
currentTutorial
andcurrentIndex
We also need to use 3 TutorialDataService
methods:
getAll()
deleteAll()
findByTitle()
components/tutorials-list.component.js
import React, { Component } from "react";
import TutorialDataService from "../services/tutorial.service";
import { Link } from "react-router-dom";
export default class TutorialsList extends Component {
constructor(props) {
super(props);
this.onChangeSearchTitle = this.onChangeSearchTitle.bind(this);
this.retrieveTutorials = this.retrieveTutorials.bind(this);
this.refreshList = this.refreshList.bind(this);
this.setActiveTutorial = this.setActiveTutorial.bind(this);
this.removeAllTutorials = this.removeAllTutorials.bind(this);
this.searchTitle = this.searchTitle.bind(this);
this.state = {
tutorials: [],
currentTutorial: null,
currentIndex: -1,
searchTitle: ""
};
}
componentDidMount() {
this.retrieveTutorials();
}
onChangeSearchTitle(e) {
const searchTitle = e.target.value;
this.setState({
searchTitle: searchTitle
});
}
retrieveTutorials() {
TutorialDataService.getAll()
.then(response => {
this.setState({
tutorials: response.data
});
console.log(response.data);
})
.catch(e => {
console.log(e);
});
}
refreshList() {
this.retrieveTutorials();
this.setState({
currentTutorial: null,
currentIndex: -1
});
}
setActiveTutorial(tutorial, index) {
this.setState({
currentTutorial: tutorial,
currentIndex: index
});
}
removeAllTutorials() {
TutorialDataService.deleteAll()
.then(response => {
console.log(response.data);
this.refreshList();
})
.catch(e => {
console.log(e);
});
}
searchTitle() {
TutorialDataService.findByTitle(this.state.searchTitle)
.then(response => {
this.setState({
tutorials: response.data
});
console.log(response.data);
})
.catch(e => {
console.log(e);
});
}
render() {
// ...
}
}
Let’s continue to implement render()
method:
// ...
import { Link } from "react-router-dom";
export default class TutorialsList extends Component {
// ...
render() {
const { searchTitle, tutorials, currentTutorial, currentIndex } = this.state;
return (
<div className="list row">
<div className="col-md-8">
<div className="input-group mb-3">
<input
type="text"
className="form-control"
placeholder="Search by title"
value={searchTitle}
onChange={this.onChangeSearchTitle}
/>
<div className="input-group-append">
<button
className="btn btn-outline-secondary"
type="button"
onClick={this.searchTitle}
>
Search
</button>
</div>
</div>
</div>
<div className="col-md-6">
<h4>Tutorials List</h4>
<ul className="list-group">
{tutorials &&
tutorials.map((tutorial, index) => (
<li
className={
"list-group-item " +
(index === currentIndex ? "active" : "")
}
onClick={() => this.setActiveTutorial(tutorial, index)}
key={index}
>
{tutorial.title}
</li>
))}
</ul>
<button
className="m-3 btn btn-sm btn-danger"
onClick={this.removeAllTutorials}
>
Remove All
</button>
</div>
<div className="col-md-6">
{currentTutorial ? (
<div>
<h4>Tutorial</h4>
<div>
<label>
<strong>Title:</strong>
</label>{" "}
{currentTutorial.title}
</div>
<div>
<label>
<strong>Description:</strong>
</label>{" "}
{currentTutorial.description}
</div>
<div>
<label>
<strong>Status:</strong>
</label>{" "}
{currentTutorial.published ? "Published" : "Pending"}
</div>
<Link
to={"/tutorials/" + currentTutorial.id}
className="badge badge-warning"
>
Edit
</Link>
</div>
) : (
<div>
<br />
<p>Please click on a Tutorial...</p>
</div>
)}
</div>
</div>
);
}
}
If you click on Edit button of any Tutorial, the app will direct you to Tutorial page.
We use React Router Link
for accessing that page with url: /tutorials/:id
.
Item details Component
We’re gonna use the component lifecycle method: componentDidMount()
to fetch the data from the Web API.
For getting data & update, delete the Tutorial, this component will use 3 TutorialDataService
methods:
get()
update()
delete()
components/tutorial.component.js
import React, { Component } from "react";
import TutorialDataService from "../services/tutorial.service";
export default class Tutorial extends Component {
constructor(props) {
super(props);
this.onChangeTitle = this.onChangeTitle.bind(this);
this.onChangeDescription = this.onChangeDescription.bind(this);
this.getTutorial = this.getTutorial.bind(this);
this.updatePublished = this.updatePublished.bind(this);
this.updateTutorial = this.updateTutorial.bind(this);
this.deleteTutorial = this.deleteTutorial.bind(this);
this.state = {
currentTutorial: {
id: null,
title: "",
description: "",
published: false
},
message: ""
};
}
componentDidMount() {
this.getTutorial(this.props.match.params.id);
}
onChangeTitle(e) {
const title = e.target.value;
this.setState(function(prevState) {
return {
currentTutorial: {
...prevState.currentTutorial,
title: title
}
};
});
}
onChangeDescription(e) {
const description = e.target.value;
this.setState(prevState => ({
currentTutorial: {
...prevState.currentTutorial,
description: description
}
}));
}
getTutorial(id) {
TutorialDataService.get(id)
.then(response => {
this.setState({
currentTutorial: response.data
});
console.log(response.data);
})
.catch(e => {
console.log(e);
});
}
updatePublished(status) {
var data = {
id: this.state.currentTutorial.id,
title: this.state.currentTutorial.title,
description: this.state.currentTutorial.description,
published: status
};
TutorialDataService.update(this.state.currentTutorial.id, data)
.then(response => {
this.setState(prevState => ({
currentTutorial: {
...prevState.currentTutorial,
published: status
}
}));
console.log(response.data);
})
.catch(e => {
console.log(e);
});
}
updateTutorial() {
TutorialDataService.update(
this.state.currentTutorial.id,
this.state.currentTutorial
)
.then(response => {
console.log(response.data);
this.setState({
message: "The tutorial was updated successfully!"
});
})
.catch(e => {
console.log(e);
});
}
deleteTutorial() {
TutorialDataService.delete(this.state.currentTutorial.id)
.then(response => {
console.log(response.data);
this.props.history.push('/tutorials')
})
.catch(e => {
console.log(e);
});
}
render() {
// ...
}
}
And this is the code for render()
method:
export default class Tutorial extends Component {
// ...
render() {
const { currentTutorial } = this.state;
return (
<div>
{currentTutorial ? (
<div className="edit-form">
<h4>Tutorial</h4>
<form>
<div className="form-group">
<label htmlFor="title">Title</label>
<input
type="text"
className="form-control"
id="title"
value={currentTutorial.title}
onChange={this.onChangeTitle}
/>
</div>
<div className="form-group">
<label htmlFor="description">Description</label>
<input
type="text"
className="form-control"
id="description"
value={currentTutorial.description}
onChange={this.onChangeDescription}
/>
</div>
<div className="form-group">
<label>
<strong>Status:</strong>
</label>
{currentTutorial.published ? "Published" : "Pending"}
</div>
</form>
{currentTutorial.published ? (
<button
className="badge badge-primary mr-2"
onClick={() => this.updatePublished(false)}
>
UnPublish
</button>
) : (
<button
className="badge badge-primary mr-2"
onClick={() => this.updatePublished(true)}
>
Publish
</button>
)}
<button
className="badge badge-danger mr-2"
onClick={this.deleteTutorial}
>
Delete
</button>
<button
type="submit"
className="badge badge-success"
onClick={this.updateTutorial}
>
Update
</button>
<p>{this.state.message}</p>
</div>
) : (
<div>
<br />
<p>Please click on a Tutorial...</p>
</div>
)}
</div>
);
}
}
Add CSS style for React Components
Open src/App.css and write some CSS code as following:
.list {
text-align: left;
max-width: 750px;
margin: auto;
}
.submit-form {
max-width: 300px;
margin: auto;
}
.edit-form {
max-width: 300px;
margin: auto;
}
Configure Port for React CRUD Client with Web API
Because most of HTTP Server use CORS configuration that accepts resource sharing retrictted to some sites or ports, so we also need to configure port for our App.
In project folder, create .env file with following content:
PORT=8081
Now we’ve set our app running at port 8081
.
Run React CRUD App
You can run our App with command: npm start
.
If the process is successful, open Browser with Url: http://localhost:8081/
and check it.
This React Client will work well with following back-end Rest APIs:
– Express, Sequelize & MySQL
– Express, Sequelize & PostgreSQL
– Express & MongoDb
– Python/Django & MySQL
– Python/Django & PostgreSQL
– Python/Django & MongoDB
Conclusion
Today we’ve built a React CRUD Application successfully with React Router & Axios. Now we can consume REST APIs, display, search and modify data in a clean way. I hope you apply it in your project at ease.
Happy learning, see you again!
Source Code
You can find the complete source code for this tutorial on Github.