One of the basic things for every developer is how to go about managing Create Read Update and Delete (CRUD) operations in an application.

The app we’re going to be building is an Expense Management App with Laravel for the Backend API and React on the frontend.

Here is the Demo of the app we are going to build using laravel and React:

CRUD API with Laravel 6

Laravel is one of the most powerful frameworks to build quick and flexible web applications. To get started with building the API using Laravel, open up your terminal and navigate to a folder of your choice eg /projects and run the following the commands to generate a Laravel boilerplate named ‘expenses-manager-api’.

composer create-project --prefer-dist laravel/laravel expenses-manager-api cd expenses-manager-api composer require fruitcake/laravel-cors php artisan serve

Once this is completed, we have an expenses-manager-api folder that contains the generated Laravel starter project with the CORS package installed.

Open up the project in your favorite text editor and update the .env file with your database credentials.

Don’t forget to add the HandleCors middleware in the $middleware property of your app/Http/Kernel.php class:

protected $middleware = [ // ... FruitcakeCorsHandleCors::class, ];

Next, run the command below to publish the cors package config to your own config

php artisan vendor:publish --tag="cors"

Now that we have handled CORS, let’s delve into building our Expenses Manager API

For this part, we are going to create an Expense model with a few options:

php artisan make:model Expense --migration --resource --controller

Once this is done, open up the create_expenses_table.php file in the database/migrations folder and modify it to include a name, description and an amount fields.

public function up() { Schema::create('expenses', function (Blueprint $table) { $table->bigIncrements('id'); $table->string('name'); $table->text('description'); $table->integer('amount'); $table->timestamps(); }); }

Next, run the migrations with:

php artisan migrate

Once, the database migration process is completed, add the following routes to the routes/api.php file:

Let’s define the operations in our controller by updating the app/Http/Controllers/ExpenseController.php with the code below:

class ExpenseController extends Controller { /** * Display a listing of the resource. * * @return IlluminateHttpResponse */ public function index() { $expenses = Expense::all(); return response()->json($expenses); } /** * Store a newly created resource in storage. * * @param IlluminateHttpRequest $request * @return IlluminateHttpResponse */ public function store(Request $request) { $request->validate([ 'name' => 'required', 'amount' => 'required', 'description' => 'required' //optional if you want this to be required ]); $expense = Expense::create($request->all()); return response()->json(['message'=> 'expense created', 'expense' => $expense]); } /** * Display the specified resource. * * @param AppExpense $expense * @return IlluminateHttpResponse */ public function show(Expense $expense) { return $expense; } /** * Show the form for editing the specified resource. * * @param AppExpense $expense * @return IlluminateHttpResponse */ /** * Update the specified resource in storage. * * @param IlluminateHttpRequest $request * @param AppExpense $expense * @return IlluminateHttpResponse */ public function update(Request $request, Expense $expense) { $request->validate([ 'name' => 'required', 'amount' => 'required', 'description' => 'required' //optional if you want this to be required ]); $expense->name = $request->name(); $expense->amount = $request->amount(); $expense->description = $request->description(); $expense->save(); return response()->json([ 'message' => 'expense updated!', 'expense' => $expense ]); } /** * Remove the specified resource from storage. * * @param AppExpense $expense * @return IlluminateHttpResponse */ public function destroy(Expense $expense) { $expense->delete(); return response()->json([ 'message' => 'expense deleted' ]); } }

Now, to enable our expenses model to be writable, open up the app/Expense.php and add the include the following to specify the fields we can write to:

protected fillable = ['name', 'description', 'amount'];

Start the application by running php artisan serve and you’d see that your API is available for use with Postman or any other REST client you prefer.

Building our React CRUD Application FrontEnd

Let’s get started with building out our frontend with React, one of the most popular JavaScript frontend libraries in use today.

In a separate folder, run the following commands to install create-react-app and create a react app called expenses-manager, cd into the folder and then install Axios (an HTTP client for sending XMLHttpRequests), react-bootstrap and bootstrap as well as sweetalert2 for presenting us with lovely looking alert boxes.

npm install -g create-react-app create-react-app expenses-manager cd expenses-manager npm install axios react-bootstrap bootstrap npm install react-router-dom sweetalert2 --save

After the installation is completed, open up your src/app.js and import the following bootstrap core file to the top of the code:

import 'bootstrap/dist/css/bootstrap.css';

The next step is to create the components we need in our application, open up your src folder and create a new folder in it named components , in the components folder, create the following files:

create-expense.component.js

edit-expense.component.js

expenses-listing.component.js

In the create-expense.component.js file, add the following code:

import React, { Component } from "react"; import Form from 'react-bootstrap/Form' import Button from 'react-bootstrap/Button' import Row from 'react-bootstrap/Row'; import Col from 'react-bootstrap/Col'; import axios from 'axios' import ExpensesList from './expenses-listing.component'; import Swal from 'sweetalert2'; export default class CreateExpense extends Component { constructor(props) { super(props) // Setting up functions this.onChangeExpenseName = this.onChangeExpenseName.bind(this); this.onChangeExpenseAmount = this.onChangeExpenseAmount.bind(this); this.onChangeExpenseDescription = this.onChangeExpenseDescription.bind(this); this.onSubmit = this.onSubmit.bind(this); // Setting up state this.state = { name: '', description: '', amount: '' } } onChangeExpenseName(e) { this.setState({name: e.target.value}) } onChangeExpenseAmount(e) { this.setState({amount: e.target.value}) } onChangeExpenseDescription(e) { this.setState({description: e.target.value}) } onSubmit(e) { e.preventDefault() const expense = { name: this.state.name, amount: this.state.amount, description: this.state.description }; axios.post('http://localhost:8000/api/expenses/', expense) .then(res => console.log(res.data)); // console.log(`Expense successfully created!`); // console.log(`Name: ${this.state.name}`); // console.log(`Amount: ${this.state.amount}`); // console.log(`Description: ${this.state.description}`); Swal.fire( 'Good job!', 'Expense Added Successfully', 'success' ) this.setState({name: '', amount: '', description: ''}) } render() { return (<div className="form-wrapper"> <Form onSubmit={this.onSubmit}> <Row> <Col> <Form.Group controlId="Name"> <Form.Label>Name</Form.Label> <Form.Control type="text" value={this.state.name} onChange={this.onChangeExpenseName}/> </Form.Group> </Col> <Col> <Form.Group controlId="Amount"> <Form.Label>Amount</Form.Label> <Form.Control type="number" value={this.state.amount} onChange={this.onChangeExpenseAmount}/> </Form.Group> </Col> </Row> <Form.Group controlId="description"> <Form.Label>Description</Form.Label> <Form.Control as="textarea" type="textarea" value={this.state.description} onChange={this.onChangeExpenseDescription}/> </Form.Group> <Button variant="primary" size="lg" block="block" type="submit"> Add Expense </Button> </Form> <br></br> <br></br> <ExpensesList> </ExpensesList> </div>); } }

Also, go to the edit-expense.component.js and add the following:

import React, { Component } from "react"; import Form from 'react-bootstrap/Form' import Button from 'react-bootstrap/Button'; import axios from 'axios'; export default class EditExpense extends Component { constructor(props) { super(props) this.onChangeExpenseName = this.onChangeExpenseName.bind(this); this.onChangeExpenseAmount = this.onChangeExpenseAmount.bind(this); this.onChangeExpenseDescription = this.onChangeExpenseDescription.bind(this); this.onSubmit = this.onSubmit.bind(this); // State this.state = { name: '', amount: '', description: '' } } componentDidMount() { axios.get('http://localhost:8000/api/expenses/' + this.props.match.params.id) .then(res => { this.setState({ name: res.data.name, amount: res.data.amount, description: res.data.description }); }) .catch((error) => { console.log(error); }) } onChangeExpenseName(e) { this.setState({ name: e.target.value }) } onChangeExpenseAmount(e) { this.setState({ amount: e.target.value }) } onChangeExpenseDescription(e) { this.setState({ description: e.target.value }) } onSubmit(e) { e.preventDefault() const expenseObject = { name: this.state.name, amount: this.state.amount, description: this.state.description }; axios.put('http://localhost:8000/api/expenses/' + this.props.match.params.id, expenseObject) .then((res) => { console.log(res.data) console.log('Expense successfully updated') }).catch((error) => { console.log(error) }) // Redirect to Expense List this.props.history.push('/expenses-listing') } render() { return (<div className="form-wrapper"> <Form onSubmit={this.onSubmit}> <Form.Group controlId="Name"> <Form.Label>Name</Form.Label> <Form.Control type="text" value={this.state.name} onChange={this.onChangeExpenseName} /> </Form.Group> <Form.Group controlId="Amount"> <Form.Label>Amount</Form.Label> <Form.Control type="number" value={this.state.amount} onChange={this.onChangeExpenseAmount} /> </Form.Group> <Form.Group controlId="Description"> <Form.Label>Description</Form.Label> <Form.Control type="text" value={this.state.description} onChange={this.onChangeExpenseDescription} /> </Form.Group> <Button variant="danger" size="lg" block="block" type="submit"> Update Expense </Button> </Form> </div>); } }

To present our Expenses in a nice tabular format, create a file named ExpenseTableRow.js and add the code below to the file:

import React, { Component } from 'react'; import { Link } from 'react-router-dom'; import Button from 'react-bootstrap/Button'; import axios from 'axios'; export default class ExpenseTableRow extends Component { constructor(props) { super(props); this.deleteExpense = this.deleteExpense.bind(this); } deleteExpense() { axios.delete('http://localhost:8000/api/expenses/' + this.props.obj.id) .then((res) => { console.log('Expense removed deleted!') }).catch((error) => { console.log(error) }) } render() { return ( <tr> <td>{this.props.obj.name}</td> <td>{this.props.obj.amount}</td> <td>{this.props.obj.description}</td> <td> <Link className="edit-link" to={"/edit-expense/" + this.props.obj.id}> <Button size="sm" variant="info">Edit</Button> </Link> <Button onClick={this.deleteExpense} size="sm" variant="danger">Delete</Button> </td> </tr> ); } }

Finally, go to the expenses-listing.component.js file and add the following:

import React, { Component } from "react"; import axios from 'axios'; import Table from 'react-bootstrap/Table'; import ExpenseTableRow from './ExpenseTableRow'; export default class ExpenseList extends Component { constructor(props) { super(props) this.state = { expenses: [] }; } componentDidMount() { axios.get('http://localhost:8000/api/expenses/') .then(res => { this.setState({ expenses: res.data }); }) .catch((error) => { console.log(error); }) } DataTable() { return this.state.expenses.map((res, i) => { return <ExpenseTableRow obj={res} key={i} />; }); } render() { return (<div className="table-wrapper"> <Table striped bordered hover> <thead> <tr> <th>Name</th> <th>Amount</th> <th>Description</th> <th>Action</th> </tr> </thead> <tbody> {this.DataTable()} </tbody> </Table> </div>); } }

Using React Router

Let’s add some routing to our app. open up the index.js file in your src directory and modify it as thus:

import React from "react"; import ReactDOM from "react-dom"; import { BrowserRouter } from "react-router-dom"; import "./index.css"; import App from "./App"; import * as serviceWorker from "./serviceWorker"; ReactDOM.render( <BrowserRouter> <App /> </BrowserRouter>, document.getElementById("root") ); // If you want your app to work offline and load faster, you can change // unregister() to register() below. Note this comes with some pitfalls. // Learn more about service workers: https://bit.ly/CRA-PWA serviceWorker.unregister();

Next, open src/app.js and change it to the following:

import React from "react"; import Nav from "react-bootstrap/Nav"; import Navbar from "react-bootstrap/Navbar"; import Container from "react-bootstrap/Container"; import Row from "react-bootstrap/Row"; import Col from "react-bootstrap/Col"; import "bootstrap/dist/css/bootstrap.css"; import "./App.css"; import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom"; import EditExpense from "./components/edit-expense.component"; import ExpensesList from "./components/expenses-listing.component"; import CreateExpense from "./components/create-expense.component"; function App() { return (<Router> <div className="App"> <header className="App-header"> <Navbar bg="success" variant="success"> <Container> <Navbar.Brand> <Link to={"/create-expense"} className="nav-link"> Expense manager </Link> </Navbar.Brand> <Nav className="justify-content-end"> <Nav> <Link to={"/create-expense"} className="nav-link"> Create Expense </Link> <Link to={"/expenses-listing"} className="nav-link"> Expenses List </Link> </Nav> </Nav> </Container> </Navbar> </header> <Container> <Row> <Col md={12}> <div className="wrapper"> <Switch> <Route exact path='/' component={CreateExpense} /> <Route path="/create-expense" component={CreateExpense} /> <Route path="/edit-expense/:id" component={EditExpense} /> <Route path="/expenses-listing" component={ExpensesList} /> </Switch> </div> </Col> </Row> </Container> </div> </Router>); } export default App;

Run npm start to preview your application.

Conclusion:

I really do hope that you were able to gain a bit of knowledge concerning Laravel and React. If you have questions about any of the steps or if you feel there is a better way to have gone about one of the concepts, please do leave a comment below.

Links:

Remember to Follow me on Twitter and Github.