Laravel 5.5 và Reactjs: Xây dựng CRUD (Create, Read, Update, Delete) từ đầu

Trước khi vào bài này bạn cần biết về tính năng Front end preset của Laravel. Laravel 5.5 đem đến một preset mới là Reactjs, bài hướng dẫn này sẽ giúp anh em tiết kiệm thời gian.

1.Cài đặt Laravel 5.5 và cấu hình DB

Chạy lệnh:

composer create-project --prefer-dist laravel/laravel LaravelReactJSTuts

Sau khi cài đặt xong, chúng ta cần cài đặt Javascript dependencies cho project. Chạy lệnh:

npm install

Bây giờ mình tạo 1 database trong phpMyAdmin hay MySQL work bench (Mac thì cài Sequel pro mà quản lý DB).

Cấu hình thông số DB trong file .env:

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=LaravelReactjstuts
DB_USERNAME=root
DB_PASSWORD=mysql

Bước tiếp theo chúng ta cd vào root folder bằng terminal rồi chạy lệnh migrate

php artisan migrate

2.React Preset

Bạn có thể sử dụng lệnh preset với tùy chọn là react:

php artisan preset react

Lệnh này sẽ tạo cho bạn một khung sườn cơ bản (scaffold). Bạn có thể compile assets bằng cách sử dụng command

npm install && npm run dev

Mở terminal và chạy lệnh:

npm run dev

Để run project chúng ta chạy lệnh

php artisan serve

Đầu tiên chúng ta cần cài gói react-router cho phần điều hướng.

npm install [email protected]

Theo kinh nghiệm thì mình cài version cũ hơn 1 xíu để ổn định. Mở terminal và chạy lệnh:

npm run watch

Nó sẽ xem những thay đổi trong thư mục assets và và tự động compile lại. Bây giờ mình sẽ chỉnh sửa lại cái ReactJS scaffold theo ý mình bằng cách paste đoạn code bên dưới vào App.js:

// app.js

require('./bootstrap');
import React from 'react';
import { render } from 'react-dom';
import { Router, Route, browserHistory } from 'react-router';

import Example from './components/Example';


Thay đổi tiếp theo là modify Example.js component:

// Example.js

import React, { Component } from 'react';

export default class Example extends Component {
    render() {
        return (

                                I am an example component!
        );
    }
}

Sau khi save, Laravel Mix sẽ compile lại tất cả các assets và build 1 bundled chúng tôi  file. Mở trình duyệt và refresh lại page thì bạn sẽ không thấy sự thay đổi gì cả, nhưng bên trong nó vẫn chạy theo flow quy định của chúng ta.

Bây giờ chúng ta cần tạo một component khác gọi là Master.js bên trong components folder.

/ Master.js

import React, {Component} from 'react';
import { Router, Route, Link } from 'react-router';

class Master extends Component {
  render(){
    return (
              {this.props.children}
    )
  }
}
export default Master;

Tiếp tục lại modify app.js một lần nữa và để component này như là root component.

// app.js

require('./bootstrap');
import React from 'react';
import { render } from 'react-dom';
import { Router, Route, browserHistory } from 'react-router';

import Master from './components/Master';


4.Cấu hình ReactJS routes

Tạo thêm 3 components trong component folder bao gồm:

  1. CreateItem.js
  2. DisplayItem.js
  3. EditItem.js

Rồi ta tạo form trong CreateItem.js để save item data

// CreateItem.js

import React, {Component} from 'react';

class CreateItem extends Component {
    render() {
      return (
      )
    }
}
export default CreateItem;

Chúng ta cần cấu hình route trong app.js

// app.js

require('./bootstrap');
import React from 'react';
import { render } from 'react-dom';
import { Router, Route, browserHistory } from 'react-router';

import Master from './components/Master';
import CreateItem from './components/CreateItem';

render(
        document.getElementById('example'));

Thử nhấp vào Create Item xem sao:

5.Sử dụng axios để làm AJAX Post request

Thêm một số event để lấy input data từ form và gửi đến server bằng AJAX post request.

// CreateItem.js

import React, {Component} from 'react';

class CreateItem extends Component {
  constructor(props){
    super(props);
    this.state = {productName: '', productPrice: ''};

    this.handleChange1 = this.handleChange1.bind(this);
    this.handleChange2 = this.handleChange2.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }
  handleChange1(e){
    this.setState({
      productName: e.target.value
    })
  }
  handleChange2(e){
    this.setState({
      productPrice: e.target.value
    })
  }
  handleSubmit(e){
    e.preventDefault();
    const products = {
      name: this.state.productName,
      price: this.state.productPrice
    }
      // browserHistory.push('/display-item');
    });
  }

    render() {
      return (
      )
    }
}
export default CreateItem;

6.Backend dùng Laravel (tất nhiên)

Chúng ta sẽ quản lý các hoạt động CRUD (Create, Read, Update, Delete) của item data. Trước mắt cần tạo controller và routes. Chạy lệnh:

php artisan make:model Item -m
<?php

// create_items_table

use IlluminateSupportFacadesSchema;
use IlluminateDatabaseSchemaBlueprint;
use IlluminateDatabaseMigrationsMigration;

class CreateItemsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('items', function (Blueprint $table) {
        });
    }

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

Rồi chạy lệnh

php artisan migrate

Lệnh này tạo bảng items trong DB. Ngoài ra, 1 file model được tạo trong folder app tên là Item.php

Cần phải tạo một controller là ItemController

php artisan make:controller ItemController --resource

ItemController chứa đựng tất cả các hàm về quản lý CRUD. Mình thử thêm code vào xem sao:

<?php

// ItemController.php

namespace AppHttpControllers;

use IlluminateHttpRequest;
use AppItem;

class ItemController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return IlluminateHttpResponse
     */
    public function index()
    {
        $items = Item::all();
    }

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

    /**
     * Store a newly created resource in storage.
     *
     * @param  IlluminateHttpRequest  $request
     * @return IlluminateHttpResponse
     */
    public function store(Request $request)
    {
        $item = new Item([
        ]);
    }

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

    /**
     * Show the form for editing the specified resource.
     *
     * @param  int  $id
     * @return IlluminateHttpResponse
     */
    public function edit($id)
    {
        $item = Item::find($id);
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  IlluminateHttpRequest  $request
     * @param  int  $id
     * @return IlluminateHttpResponse
     */
    public function update(Request $request, $id)
    {
        $item = Item::find($id);

    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return IlluminateHttpResponse
     */
    public function destroy($id)
    {
      $item = Item::find($id);

    }
}

Chúng ta cũng cần tạo 1 protected field $fillable trong chúng tôi nếu không thì nó sẽ báo ‘mass assignment exception

<?php

// Item.php

namespace App;

use IlluminateDatabaseEloquentModel;

class Item extends Model
{
    protected $fillable = ['name', 'price'];
}
<?php

/*
| Web Routes
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', function () {
    return view('welcome');
});
Route::resource('items', 'ItemController');

Nếu bạn insert giá trị vào DB, sẽ có thể bạn gặp vấn đề này:

Error này là do vấn đề bảo mật CORS. Giải pháp là ta cần allow origin cho Laravel server side. Tạo 1 middleware tại backend và dùng nó cho tất cả các API request.

composer require barryvdh/laravel-cors

Add CorsServiceProvider cho config/app.php

BarryvdhCorsServiceProvider::class,

Để allow CORS cho tất cả route, add HandleCors trong        $middleware của app/Http/Kernel.php

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

Rồi chạy lệnh:

php artisan vendor:publish --provider="BarryvdhCorsServiceProvider"

Xong, bây giờ bạn thử save data vào DB thử xem.

7.Hiển thị data ra ReactJS Frontend

// DisplayItem.js

import React, {Component} from 'react';
import axios from 'axios';
import { Link } from 'react-router';
import TableRow from './TableRow';

class DisplayItem extends Component {
  constructor(props) {
       super(props);
       this.state = {value: '', items: ''};
     }
     componentDidMount(){
         this.setState({ items: chúng tôi });
       })
       .catch(function (error) {
         console.log(error);
       })
     }
     tabRow(){
       if(this.state.items instanceof Array){
         return this.state.items.map(function(object, i){
         })
       }
     }

  render(){
    return (


              {this.tabRow()}
    )
  }
}
export default DisplayItem;

Tạo thêm TableRow.js component.

// TableRow.js

import React, { Component } from 'react';

class TableRow extends Component {
  render() {
    return (
            {this.props.obj.id}
            {this.props.obj.name}
            {this.props.obj.price}
    );
  }
}

export default TableRow;
// app.js

import DisplayItem from './components/DisplayItem';

render(
        document.getElementById('example'));

Chúng ta cần thay đổi 1 thứ nữa là cần phải redirect đến component này sau khi save data. Vì vậy trong tập tin CreateItem.js, sửa code như sau:

// CreateItem.js

import {browserHistory} from 'react-router';

      browserHistory.push('/display-item');
    });

Mở EditItem.js :

// EditItem.js

import React, {Component} from 'react';
import axios from 'axios';
import { Link } from 'react-router';

class EditItem extends Component {
  constructor(props) {
      super(props);
      this.state = {name: '', price: ''};
      this.handleChange1 = this.handleChange1.bind(this);
      this.handleChange2 = this.handleChange2.bind(this);
      this.handleSubmit = this.handleSubmit.bind(this);
  }

  componentDidMount(){
      this.setState({ name: response.data.name, price: response.data.price });
    })
    .catch(function (error) {
      console.log(error);
    })
  }
  handleChange1(e){
    this.setState({
      name: e.target.value
    })
  }
  handleChange2(e){
    this.setState({
      price: e.target.value
    })
  }

  handleSubmit(event) {
    event.preventDefault();
    const products = {
      name: this.state.name,
      price: this.state.price
    }
          this.props.history.push('/display-item');
    });
  }
  render(){
    return (
                <input type="text"
                  className="form-control"
                  value={this.state.name}

                <input type="text" className="form-control"
                  value={this.state.price}

    )
  }
}
export default EditItem;

và register route trong app.js:

// app.js

import EditItem from './components/EditItem';

render(
        document.getElementById('example'));

Update thêm cho  chúng tôi

9. Xóa data

Để xóa data, chúng ta cần define hàm delete trong  chúng tôi

// TableRow.js

import React, { Component } from 'react';
import { Link, browserHistory } from 'react-router';

class TableRow extends Component {
  constructor(props) {
      super(props);
      this.handleSubmit = this.handleSubmit.bind(this);
  }
  handleSubmit(event) {
    event.preventDefault();
    axios.delete(uri);
      browserHistory.push('/display-item');
  }
  render() {
    return (
            {this.props.obj.id}
            {this.props.obj.name}
            {this.props.obj.price}
    );
  }
}

export default TableRow;