Xây dựng ứng dụng crud với laravel 5 và reactjs

1.Cài đặt Laravel 5
Ta sẽ tạo project tên là reactjs_demo
Ở đây mình dùng laravel 5.4

composer create-project --prefer-dist laravel/laravel reactjs_demo "5.4.*"

Để remove folder public các bạn làm theo hướng dẫn này của mình
Các bạn open file package.json và remove line 19 vue
Copy toàn bộ code sau đây vào file package.json:

{
  "private": true,
  "scripts": {
    "dev": "npm run development",
    "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
    "watch": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --watch --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
    "watch-poll": "npm run watch -- --watch-poll",
    "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
    "prod": "npm run production",
    "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
  },
  "devDependencies": {
    "axios": "^0.16.2",
    "bootstrap-sass": "^3.3.7",
    "cross-env": "^5.0.1",
    "jquery": "^3.1.1",
    "laravel-mix": "^1.0",
    "lodash": "^4.17.4"
  }
}

Cài đặt các package dependencies:

npm install
npm install --save react react-dom
npm install --save react-router-dom
npm install --save-dev babel-preset-stage-1
npm install --save-dev @babel/preset-env
npm install --save-dev babel-plugin-transform-class-properties 
npm install --save-dev babel-preset-react
npm i react-bootstrap-dialog --save
npm install moment
npm install react-bootstrap bootstrap
npm install react-spinner-material

Cài đặt laravel-mix, trong file webpack.min.js
Thay đổi:

mix.js('resources/assets/js/app.js', 'public/js')
   .sass('resources/assets/sass/app.scss', 'public/css');

thành:

mix.react('resources/assets/js/app.js', 'public/js')
   .sass('resources/assets/sass/app.scss', 'public/css');

Thêm .babelrc file trong thư mục của project

{
 "plugins": ["transform-class-properties"],
 "presets": [["env", {"modules": false}],
 "react"]
}

Sửa file D:\xampp\htdocs\reactjs_demo\resources\views\welcome.blade.php thành

<!doctype html>
<html lang="{{ app()->getLocale() }}">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <title>Laravel</title>
         <script type="text/javascript">
            window.Laravel = {!! json_encode([
                'baseUrl' => url('/'),
                'csrfToken' => csrf_token(),
            ]) !!};
        </script>
        <!-- Fonts -->
        <link href="https://fonts.googleapis.com/css?family=Raleway:100,600" rel="stylesheet" type="text/css">

        <!-- Styles -->
        <!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
        {{-- <link rel="stylesheet" type="text/css" href="{{ asset('public/css/app.css') }}"> --}}
    </head>
    <body>
        <div class="flex-center position-ref full-height">
            <div class="content">
                <div id="example"></div>
            </div>
        </div>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
        <script type="text/javascript" src="{{ asset('public/js/app.js') }}?v={{uniqid()}}"></script>
    </body>
</html>

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

npm run watch

Mở file reactjs_demo\resources\assets\js\app.js sửa thành:

import React from 'react';
import ReactDom from 'react-dom';


 ReactDom.render(
   <h1>Hello world</h1>,
   document.getElementById('example')
 );

Mở browser và gõ : http://localhost/reactjs_demo/ nếu bạn thấy chử “Hello world” thì chúng ta đã setup thành công
Bạn vào thư mục reactjs_demo\resources\assets\js\components xóa file Example.vue và tạo tiếp 5 file sau:
Master.js
CreateNew.js
EditNew.js
NewList.js
NewRow.js

Tạo layout cho project bạn tạo file Master.js như sau:

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

class Master extends Component {
  render () {
    return (
      <div>
        <nav className='navbar navbar-default navbar-static-top'>
          <div className='container'>
            <div className='navbar-header'>
              <button type='button' className='navbar-toggle collapsed' data-toggle='collapse' data-target='#app-navbar-collapse' aria-expanded='false'>
                <span className='sr-only'>Toggle Navigation</span>
                <span className='icon-bar' />
                <span className='icon-bar' />
                <span className='icon-bar' />
              </button>
              <Link className='navbar-brand' to='/'>
                Laravel 5.5 - ReactJS Example
              </Link>
            </div>
            <div className='collapse navbar-collapse' id='app-navbar-collapse'>
              <ul className='nav navbar-nav'>
                <li><Link to='/'>Tin tức</Link></li>
                <li><Link to='/create-title'>Thêm tin tức</Link></li>
              </ul>
            </div>
          </div>
        </nav>
        <div className='container'>
          {this.props.children}
        </div>
      </div>
    )
  }
}
export default Master

File CreateNew.js:

import React, { Component } from 'react'
import Master from './Master'
import axios from 'axios'
import { withRouter } from "react-router-dom";
const configPost = {
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded'
  }
}
class CreateNew extends Component {
  constructor (props) {
    super(props)
    this.state = {
      title: ''
    }

    this.handleChangeTitle = this.handleChangeTitle.bind(this)
    this.handleSubmit = this.handleSubmit.bind(this)
    this.demoBack = this.demoBack.bind(this)
  }

  handleChangeTitle(e) {
    this.setState({
      title: e.target.value
    })
  }


  handleSubmit (e) {
    e.preventDefault()
    let url = window.Laravel.baseUrl + '/api/create-title'
    const data = {
      title: this.state.title
    }
    axios.post(url, data,configPost)
      .then(response => {
        this.props.history.push('/')
      })
      .catch(function (error) {
        console.log(error)
      })
  }

  demoBack(){
  	 this.props.history.push("/");
  }

  render () {
    return (
      <Master>
        <h1>Thêm tin tức</h1>
        <form onSubmit={this.handleSubmit}>
          <div className='form-group'>
            <label htmlFor='title'>Tiêu đề</label>
            <input type='text' className='form-control' id='title' placeholder='Title'
              value={this.state.title} onChange={this.handleChangeTitle} required />
          </div>
          <button type='submit' className='btn btn-primary' style={{marginRight: '5px'}}>Thêm</button>
          <button  className='btn btn-primary' onClick={this.demoBack}>Back</button>
        </form>
      </Master>
    )
  }
}
export default withRouter(CreateNew);

File EditNew.js:

import React, { Component } from 'react'
import Master from './Master'
import axios from 'axios'
import { withRouter } from "react-router-dom";
import Spinner from 'react-spinner-material';
const configPost = {
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded'
  }
}
class EditNew extends Component {
  constructor (props) {
    super(props)
    this.state = {
      title: '',
      isLoading:true
    }

    this.handleChangeTitle = this.handleChangeTitle.bind(this)
    this.handleSubmit = this.handleSubmit.bind(this)
    this.demoBack = this.demoBack.bind(this)
  }

  componentDidMount () {
    let url = window.Laravel.baseUrl + '/api/edit/' + this.props.match.params.id
    axios.get(url)
      .then(response => {
        this.setState(response.data)
        this.setState({isLoading:false})
      })
      .catch(function (error) {
        console.log(error)
      })
  }

  handleChangeTitle (e) {
    this.setState({
      title: e.target.value
    })
  }



  handleSubmit (e) {
    e.preventDefault()
    let url = window.Laravel.baseUrl + '/api/update';
    const data = {
      id:this.props.match.params.id,
      title: this.state.title,
    }
    axios.post(url, data,configPost)
      .then(response => {
        this.props.history.push('/')
      })
      .catch(function (error) {
        console.log(error)
      })
  }

  demoBack(){
     this.props.history.push("/");
  }

  showLoading(){
    return (<Spinner size={120} spinnerColor={"red"} spinnerWidth={2} visible={true} />)
  }

  showFormEdit(){
    return <form onSubmit={this.handleSubmit}>
          <div className='form-group'>
            <label htmlFor='title'>Tiêu đề</label>
            <input type='text' className='form-control' id='title' placeholder='title'
              value={this.state.title} onChange={this.handleChangeTitle} required />
          </div>
          <button type='submit' className='btn btn-primary' style={{marginRight: '5px'}}>Sửa</button>
          <button  className='btn btn-primary' onClick={this.demoBack}>Back</button>
        </form>
  }

  render () {
    return (
      <Master>
        <h1>Sửa tin tức</h1>
        {this.state.isLoading?(this.showLoading()):this.showFormEdit()}
      </Master>
    )
  }
}
export default withRouter(EditNew);

File NewList.js:

import React, { Component } from 'react'
import axios from 'axios'
import { Link } from 'react-router-dom'
import Spinner from 'react-spinner-material';
import Dialog from 'react-bootstrap-dialog';
import Master from './Master'
import NewRow from './NewRow'

class NewList extends Component {
  constructor (props) {
    super(props)
    this.state = { news: '',isLoading:true }
  }
  componentDidMount () {
    axios.get(window.Laravel.baseUrl + '/api/home')
      .then(response => {
        this.setState({ news: response.data.data,isLoading:false })
      })
      .catch(function (error) {
        console.log(error)
      })
  }


  deleteRow (key) {
    var news = [...this.state.news];
    news.splice(key, 1);
    this.setState( {news} );
  }

  fetchRows () {
    if (this.state.news instanceof Array) {
      return this.state.news.map( (object, i) => {
        return <NewRow obj={object} key={i}  index={i} alertForm={this.dialog} deleteRow={ this.deleteRow.bind(this) } />
      })
    }
  }

  showLoading(){
    return (<tr><td colSpan={3} align="center"><Spinner size={120} spinnerColor={"red"} spinnerWidth={2} visible={true} /></td></tr>)
  }

  render () {
    return (
      <Master>
        <h1>Danh sách tin tức</h1>
        <div className='clearfix'>
          <Link className='btn btn-success pull-right' to='/create-title'>Thêm tin tức</Link>
        </div>
        <table className='table table-hover'>
          <thead>
            <tr>
              <td>STT</td>
              <td>Tiêu đề</td>
              <td>Ngày tạo</td>
            </tr>
          </thead>
          <tbody>
           {this.state.isLoading?(this.showLoading()):this.fetchRows()}
          </tbody>
        </table>
        <Dialog ref={(component) => { this.dialog = component }} />
      </Master>
    )
  }
}
export default NewList

File NewRow.js:

import React, { Component } from 'react'
import { Link } from 'react-router-dom'
import axios from 'axios'
import Dialog from 'react-bootstrap-dialog';
class NewRow extends Component {
  constructor (props) {
    super(props)
    this.handleDelete = this.handleDelete.bind(this)
  }


  handleDelete (e) {
    e.preventDefault()
    this.props.alertForm.show({
      body: 'Are you sure?',
      actions: [
        Dialog.OKAction(() => {
          let url = window.Laravel.baseUrl + '/api/delete/' + this.props.obj.id
          axios.delete(url)
            .then(response => {
              this.props.deleteRow(this.props.index);
            })
            .catch(function (error) {
              console.log(error)
            })
        }),
        Dialog.CancelAction(()=>{
        }),

      ]
    })

  }
  render () {
    return (
      <tr>
        <td>
          {this.props.index+1}
        </td>
        <td>
          {this.props.obj.title}
        </td>
        <td>
          {moment(this.props.obj.created_at).format("DD-MM-YYYY hh:mm:ss") }
        </td>
        <td>
          <Link className='btn btn-primary' to={'/users/edit/' + this.props.obj.id}>Edit</Link>
        </td>
        <td>
          <button className='btn btn-danger' onClick={this.handleDelete}>Delete</button>
        </td>
      </tr>
    )
  }
}

export default NewRow

Sửa file reactjs_demo\resources\assets\js\app.js:

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Router , Route, Switch } from 'react-router-dom';
import { createBrowserHistory } from 'history';
import NewList from './components/NewList';
import CreateNew from './components/CreateNew';
import EditNew from './components/EditNew';
window.moment = require('moment');
const history = createBrowserHistory({
    basename: 'reactjs_demo'
});
class App extends Component {
  render() {
    return (
      <Router  history={history}>
        <Switch>
          <Route exact  path='/' component={NewList} />
          <Route path='/create-title' component={CreateNew} />
          <Route path='/users/edit/:id' component={EditNew} />
	    </Switch>
      </Router>
    );
  }
}

ReactDOM.render(<App />, document.getElementById('example'));

Các bạn chú ý basename là tên folder root web của bạn
Backend Laravel:
Các bạn tự cấu hình database nhé
Tạo modal news:

php artisan make:model News

Sửa file News.php thành:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class News extends Model
{
    protected $table='news';

    protected $fillable = ['title'];
}

Tạo controller new:

php artisan make:controller NewController

Sửa file NewController:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\News;

class NewController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        $new = News::where('is_delete',0)->orderBy('id', 'DESC')->paginate(5);
        return response()
            ->json($new);
    }

    public function createTitle(Request $request)
    {
        $result = json_decode($request->getContent());
        News::create(['title'=>$result->title]);
        return response()
            ->json(['message' => 'Success: You have added new']);
    }

    public function delete(Request $request)
    {
        $news = News::find($request->id);
        if (! $news) {
            return response()
            ->json(['error' => 'Error: User not found']);
        }
        $news->is_delete=1;
        $news->save();
        return response()
            ->json(['message' => 'Success: You have deleted the user']);
    }

    public function edit($id)
    {
        $news = News::find($id);
        if (! $news) {
            return response()
            ->json(['error' => 'The user is not exists']);
        }
        return response()
            ->json($news);
    }

    public function update(Request $request)
    {
        $result = json_decode($request->getContent());
        $news = News::find($result->id);
        if (! $news) {
            return response()
            ->json(['error' => 'Error: User not found']);
        }
        $news->update(['title'=>$result->title]);
        return response()
            ->json(['message' => 'Success: You have updated the user']);
    }
}

Mở command run:

npm run watch

Mở broser truy cập http://localhost/reactjs_demo

Link code full: https://github.com/thaiviethai99/crud_reactjs_laravel_no_paging
Table news là file : news_react_js_no_paging.sql trong thu muc code github

Các bạn download file sau và giải nén vào thư mục node_modules:http://gacoder.info/download/react-bootstrap.rar