Xây dựng ứng dụng đơn giản với ReactJS và Laravel

Xin chào các bạn, hôm nay mình sẽ tiếp tục làm ví dụ đơn giản làm App Todo cho Reactjs với RESTful sử dụng Laravel.

Nội dung

Khởi tạo server bằng Laravel

Step 1: Khởi tạo project:

$ composer create-project --prefer-dist laravel/laravel laract "5.5.*"

Ở đây mình sử dụng Laravel 5.5 😄 với laract là tên của project.

Bạn nhớ chỉnh sửa file .env cho phù hợp với kết nối của bạn nhé.

Step 2: Tạo migration

$ php artisan make:migration create_products_table

Trong file migration vừa tạo tại đường dẫn laract/database/migrations/ bạn sửa như sau:

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateProductsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('products', function (Blueprint $table) {
            $table->increments('id');
            $table->string('title');
            $table->text('body');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('products');
    }
}

Xong bạn nhớ sử dụng lệnh này nhé:

$ php artisan migrate

Step 3: Tạo model

$ php artisan make:model Product

Bạn vào file Product.php trong thư mục app và chỉnh sửa như sau:

<?php


namespace App;


use Illuminate\Database\Eloquent\Model;


class Product extends Model
{
    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'title', 'body'
    ];
}

Tìm hiểu thêm về Mass Asignment trong Laravel

Step 4: Tạo API

Vào thư mục routes/api đăng kí Api:

Route::resource('products', 'ProductController');

Step 5: Tạo Controller

Tiếp theo chúng ta sẽ tạo một Resource Controller:

php artisan make:controller ProductController -resource

Tìm hiểu thêm về Resource Controller trong Laravel.

Sau khi tạo Controller, bạn vào thư mục app/Http/Controller/ProductController sẽ thấy có 7 action. Bạn sửa như sau:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Product;

class ProductController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        $products = Product::all();
        return response()->json($products);
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        //
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        $product = new Product([
            'title' => $request->get('title'),
            'body' => $request->get('body')
          ]);
          $product->save();
  
  
        return response()->json('Product Added Successfully.');
    }

    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function show($id)
    {
        //
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function edit($id)
    {
        $product = Product::find($id);
        return response()->json($product);
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, $id)
    {
        $product = Product::find($id);
        $product->title = $request->get('title');
        $product->body = $request->get('body');
        $product->save();
        return response()->json('Product Updated Successfully.');
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function destroy($id)
    {
        $product = Product::find($id);
        $product->delete();


        return response()->json('Product Deleted Successfully.');
    }
}

OK, vậy là ta đã tạo xong phần backend.

Tạo front-end bằng react-app

Step 1: Tạo react-app

Mở terminal lên và làm như sau:

$ npx create-react-app react-app

Với react-app là tên của project. Giờ bạn hãy thử xem app của bạn có chạy được không bằng cách:

$ cd react-app
$ npm start

Sau khi chạy 2 lệnh này thì trình duyệt sẽ tự động mở một đường dẫn có địa chỉ localhost:3000 có giao diện dưới đây: -

Tiếp theo, bạn copy vào file package.json

{
  "name": "react-complete-guide",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "axios": "^0.18.0",
    "bootstrap": "^4.0.0",
    "react": "^16.0.0",
    "react-dom": "^16.0.0",
    "react-router": "^4.2.0",
    "react-router-dom": "^4.2.2",
    "react-scripts": "1.0.13"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  },
  "devDependencies": {
    "eslint": "^4.18.2",
    "eslint-plugin-react": "^7.7.0"
  }
}

Sau đó chạy npm install để cài các package trong file này. Trong thư mục src bạn tạo thư mục components. Trong components bạn tạo các file sau:

components/Product/CreateProduct/CreateProduct.js :

import React, {Component} from 'react';
import axios from 'axios';
import './createProduct.css';
import config from '../../../config';

class CreateProduct extends Component {
    constructor(props){
        super(props);
        this.state = {productTitle: '', productBody: ''};
        this.handleChangeTitle = this.handleChangeTitle.bind(this);
        this.handleChangeBody = this.handleChangeBody.bind(this);
        this.handleSubmit = this.handleSubmit.bind(this);
    }
    handleChangeTitle(e){
        this.setState({
            productTitle: e.target.value
        });
    }
    handleChangeBody(e){
        this.setState({
            productBody: e.target.value
        });
    }
    handleSubmit(e){
        e.preventDefault();
        const products = {
            title: this.state.productTitle,
            body: this.state.productBody
        };
        let uri = `${config.API_SERVER_URL}products`;
        axios.post(uri, products).then((response) => {
            this.props.history.push( '/display-item', products );
        });
    }
    render() {
        return (
            <div className="container">
                <form onSubmit={this.handleSubmit}>
                    <div className="row">
                        <div className="col-25">
                            <label htmlFor="title">Product Title</label>
                        </div>
                        <div className="col-75">
                            <input type="text" onChange={this.handleChangeTitle} />
                        </div>
                    </div>
                    <div className="row">
                        <div className="col-25">
                            <label htmlFor="body">Product Body</label>
                        </div>
                        <div className="col-75">
                            <input type="text" onChange={this.handleChangeBody}/>
                        </div>
                    </div>
                    <div className="row">
                        <input type="submit" value="Add product" />
                    </div>
                </form>
            </div>
        );
    }
}
export default CreateProduct;

components/Product/DisplayProduct/DisplayProduct.js :

import React, { Component } from 'react';
import Product from '../Product';
import { Link } from 'react-router-dom';
import axios from 'axios';
import { findIndex } from 'lodash';
import '../DisplayProduct/displayProduct.css';
import config from '../../../config';

class DisplayProduct extends Component {
    constructor(props) {
        super(props);
        this.state = {
            products: []
        }
        this.deleteProduct = this.deleteProduct.bind(this);
    }

    deleteProduct = (id, state) => {
        axios.delete(`${config.API_SERVER_URL}products/` + id).then((response) => {
            let currentProduct = [...this.state.products];
            let productIndex = findIndex(currentProduct, p => p.id === id);
            currentProduct.splice(productIndex, 1);
            this.setState({
                products: currentProduct
            })
        });
    }

    componentDidMount() {
        axios.get(`${config.API_SERVER_URL}products`)
            .then(response => {
                this.setState({ products: response.data });
            })
            .catch(function (error) {
                console.log(error);
            })
    }
    render() {
        const products = this.state.products.map((product) => {
            return <Product click={this.deleteProduct} key={product.id} id={product.id} title={product.title} body={product.body} />;
        })
        return (
            <div>
                <h1>Products</h1>
                <div className="row">
                    <div className="col-md-2">
                        <Link to="/add-item">Create Product</Link>
                    </div>
                </div><br />
                <table>
                    <thead>
                        <tr>
                            <th>ID</th>
                            <th>Product Title</th>
                            <th>Product Body</th>
                            <th>Edit</th>
                            <th>Delete</th>
                        </tr>
                    </thead>
                    <tbody>
                        {products}
                    </tbody>
                </table>
            </div>
        )
    }
}

export default DisplayProduct;

components/Product/UpdateProduct/UpdateProduct.js :

import React, { Component } from 'react';
import axios from 'axios';
import { Link } from 'react-router-dom';
import './updateProduct.css';
import config from '../../../config';

class UpdateProduct extends Component {
    constructor(props) {
        super(props);
        this.state = { title: '', body: '' };
        this.handleChangeTitle = this.handleChangeTitle.bind(this);
        this.handleChangeBody = this.handleChangeBody.bind(this);
        this.handleSubmit = this.handleSubmit.bind(this);
    }

    componentDidMount() {
        axios.get(`${config.API_SERVER_URL}products/${this.props.match.params.id}/edit`)
            .then(response => {
                this.setState({ title: response.data.title, body: response.data.body });
            })
            .catch(function (error) {
                console.log(error);
            })
    }
    handleChangeTitle(e) {
        this.setState({
            title: e.target.value
        })
    }
    handleChangeBody(e) {
        this.setState({
            body: e.target.value
        })
    }


    handleSubmit(event) {
        event.preventDefault();
        const products = {
            title: this.state.title,
            body: this.state.body
        }
        let uri = `${config.API_SERVER_URL}products/` + this.props.match.params.id;
        axios.put(uri, products).then((response) => {
            this.props.history.push('/display-item', products);
        });
    }
    render() {
        return (
            <div>
                <h1>Update Product</h1>
                <div>
                    <Link to="/display-item">Return to Product</Link>
                </div>
                <div className="container">
                    <form onSubmit={this.handleSubmit}>
                        <div className="row">
                            <div className="col-25">
                                <label htmlFor="title">Product Title</label>
                            </div>
                            <div className="col-75">
                                <textarea type="text"
                                    value={this.state.title}
                                    onChange={this.handleChangeTitle} />
                            </div>
                        </div>
                        <div className="row">
                            <div className="col-25">
                                <label htmlFor="body">Product Body</label>
                            </div>
                            <div className="col-75">
                                <textarea className="form-control"
                                    onChange={this.handleChangeBody} value={this.state.body}></textarea>
                            </div>
                        </div>
                        <div className="row">
                            <input type="submit" value="Update" />
                        </div>
                    </form>
                </div>
            </div>
        )
    }
}
export default UpdateProduct;

components/Product/Product.js :

import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import './product.css';

class Product extends Component {
    removeProduct = (event, id) => {
        event.preventDefault();
        this.props.click(this.props.id);
    } 
    render () {
        return (
            <tr>
                <td>
                    { this.props.id}
                </td>
                <td>
                    { this.props.title}
                </td>
                <td>
                    { this.props.body} 
                </td>
                <td>
                <Link to={"/edit/" + this.props.id} className="button button2">Edit</Link>
                </td>
                <td>
                    <form id="form-delete" onSubmit={this.removeProduct}>
                        <input type="submit" value="Delete" className="btn btn-danger"/>
                    </form>
                </td>
            </tr>
        )
    }
}

export default Product;

components/Master.js :

import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import './master.css';

class Master extends Component {
    render () {
        return (
            <div className="container">
                <ul>
                    <li><Link to="/">Home</Link></li>
                    <li><Link to="/add-item">Create Product</Link></li>
                    <li><Link to="/display-item">Products</Link></li>
                </ul>
                <h1>HELLO</h1>
                <div>
                    {this.props.children}
                </div>
            </div>
        );
    }
}

export default Master;

src/hoc/asyncComponent.js:

import React, { Component } from 'react';

const asyncComponent = (importComponent) => {
    return class extends Component {
        state = {
            component: null
        }

        componentDidMount () {
            importComponent ()
            .then(cmp => {
                this.setState({
                    component: cmp.default
                });
            }); 
        }

        render () {
            const C = this.state.component;

            return C ? <C {...this.props} /> : null;
        }
    }
}

export default asyncComponent;

src/config.js:

const config = {
    'API_SERVER_URL' : 'http://localhost:8000/api/'
}

export default config;

Bạn tạo một thư mục có tên là containers trong src. Sau đó di chuyển file App.jsApp.test.js vào trong thư mục này. Ở đây mình muốn sắp xếp thư mục cho gọn gàng và dễ nhìn nên mình làm như vậy. Vậy là xong 😄 Ở đây mình chỉ làm chức năng nên về giao diện nó không đẹp lắm. 😄

Xong rồi. Giờ các bạn vào terminal, di chuyển đến thư mục chứa source code laravel lúc đầu bạn tạo và chạy:

$ php artisan serve

Bạn mở thêm 1 terminal lên và di chuyển đến thư mục chứa source code của react app:

$ npm start

Vậy là chúng ta đã làm xong 1 ví dụ Todos về Reactjs + laravel rồi. Bài viết khá dài, cảm ơn các bạn đã theo dõi. Hi vọng bài viết có ích cho các bạn. Đây là source code trên github: Laravel + Reactjs.


All Rights Reserved