02 Juli 2018 21.23

Mekanisme Login Berbasis REST API Dengan React + Redux dan Laravel

Beberapa mahasiswa masih bingung mengimplementasikan mekanisme login menggunakan React + Redux sebagai front end dan Laravel sebagai back end. Karena itu saya akan membuat sebuah contoh aplikasi sederhana menggunakan React + Redux dan Laravel yang mengimplementasikan mekanisme login dan logout dengan REST API.

Persiapan React + Redux

Kita buat project React baru dilengkapi dengan Redux dan React Router. Bagian desain kita abaikan sementara dan hanya akan ada dua "halaman" yaitu halaman login dan halaman beranda setelah login.

create-react-app loginapp
cd loginapp

Setelah itu kita install beberapa modul tambahan yaitu redux dan router.

npm install --save react-redux react-router-dom redux redux-form redux-thunk

Jalankan aplikasi React Anda dengan perintah

npm start

Hapus beberapa file yang ada di folder src dan sisakan file App.js dan index.js. Selanjutnya modifikasi file src/index.js menjadi seperti berikut:

import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import App from './App';

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

Router

Skenario utama yang akan kita buat adalah sebagai berikut:

  1. Halaman default adalah form login dimana pengguna diminta memasukkan username dan password.

  2. Jika proses login sukses maka pengguna dipindahkan ke halaman beranda.

  3. Di halaman beranda terdapat link untuk logout yang membawa pengguna kembali ke halaman default / login.

Berdasarkan skenario tersebut kita memerlukan route dan component sebagai berikut:

PathFungsi
"/"Menampilkan form login (halaman default)
"/home"Menampilkan halaman setelah login dengan tombol logout

Redux State, Actions dan Reducer

Redux State

Redux adalah salah satu arsitektur dalam pengembangan aplikasi React yang mana terdapat sebuah state tunggal dalam keseluruhan aplikasi. State ini yang akan digunakan oleh semua komponen dalam aplikasi. Rancangan state yang akan digunakan adalah:

{
  isFetching: false,
  user: {
    id: 0,
    nama: ''
  }
}

isFetching digunakan untuk menyatakan kondisi apakah kita sedang meminta data ke server, sedangkan user digunakan untuk menampung data user yang akan dan sedang login.

Action

Action pada umumnya adalah sebuah objek sederhana yang berisi type dan payload yang bersifat opsional. Beberapa actions yang akan kita gunakan adalah:

ActionKeterangan
SET_FETCHINGMengeset status apakah sedang fetching ke server
SET_USERMengeset data pengguna dari hasil proses login
DELETE_USERMenghapus data pengguna dari state saat logout

Action diatas adalah action sederhana, kita akan menggunakan beberapa action tambahan menggunakan redux-thunk. Sekarang kita buat file baru dengan nama actions.js dengan isi seperti berikut:

export const SET_FETCHING = 'SET_FETCHING';
export const SET_USER = 'SET_USER';
export const DELETE_USER = 'DELETE_USER';

export function setFetching(v) {
  return { type: SET_FETCHING, state: v };
}

export function setUser(user) {
  return { type: SET_USER, user };
}

export function deleteUser() {
  return { type: DELETE_USER };
}

export function actionTryLogin(values) {
  // redux thunk ...

}

Reducer

Reducer akan memproses action untuk mengubah nilai state yang ada di Store. Kita buat file dengan nama reducers.js untuk menampung reducer yang kita buat seperti berikut:

import { combineReducers } from 'redux';
import { reducer as formReducer } from 'redux-form';
import {
  SET_FETCHING,
  SET_USER,
  DELETE_USER
} from './actions';

const userDefault = {
  id: 0,
  nama: ''
}

function user(state = userDefault, action) {
  switch (action.type) {
    case SET_USER:
      return Object.assign(state, action.user);
    case DELETE_USER:
      return userDefault;
    default:
      return state;
  }
}

function isFetching(state = false, action) {
  switch (action.type) {
    case SET_FETCHING: return action.state;
    default: return state;
  }
}

const loginApp = combineReducers({
  user,
  isFetching,
  form: formReducer
});

export default loginApp;

Memasukkan Store, Reducer ke App

Setelah membuat action dan reducer kita perlu mengubah file index.js dengan membuat Store dan Provider seperti kode berikut:

import React from 'react';
import ReactDOM from 'react-dom';
import { createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import { BrowserRouter } from 'react-router-dom';
import loginApp from './reducers';  
import App from './App';

const store = createStore(loginApp, applyMiddleware(thunk));
ReactDOM.render(
  <Provider store={store}>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </Provider>,
  document.getElementById('root')
);

Tampilan Page

Sekali lagi kita tidak meng handle masalah tampilan atau styling. Dua komponen "Page" yang akan kita buat adalah "LoginPage" dan "HomePage" dibuat dengan tampilan dasar sederhana. Kita perlu mengubah src/App.js menjadi seperti berikut:

import React, { Component } from 'react';
import { Switch, Route } from 'react-router-dom';

import LoginPage from './components/LoginPage';
import HomePage from './components/HomePage';

class App extends Component {
  render() {
    return (
      <div>
        <Switch>
          <Route exact path="/" component={LoginPage} />
          <Route exact path="/home" component={HomePage} />
        </Switch>
      </div>
    );
  }
}

export default App;

Komponen Switch yang menentukan komponen apa yang akan dirender jika URL sama dengan path yang telah ditentukan.

HomePage

Kita buat komponen yang lebih mudah dahulu dimana page ini hanya berisi teks Beranda. Kita akan modifikasi file ini nanti ketika kita sudah berhasil dan ingin menambahkan fungsi logout.

import React, { Component } from 'react';

class HomePage extends Component {
  
  render() {
    return (
      <div>
        <h1>BERANDA</h1>
      </div>
    );
  }
}

export default HomePage;

LoginPage

Buat file baru dengan nama LoginPage.js dan letakkan di folder components (buat folder ini lebih dahulu). Kita akan membuat form seperti berikut:

import React, { Component } from 'react';
import { reduxForm, Field } from 'redux-form';
import { Redirect } from 'react-router-dom';
import { connect } from 'react-redux';
import { actionTryLogin, setUser } from '../actions';

const renderInput = ({ placeholder, type, input }) => (
  <input
    type={type}
    placeholder={placeholder}
    {...input}
  />
);

class LoginPage extends Component {
  render() {
    const isFetching = this.props.isFetching;
    const user = this.props.user;

    return (
      <div>
        <h1>LOGIN PENGGUNA</h1>
        <form onSubmit={this.props.handleSubmit}>
          <Field name="username" component={renderInput} type="text" placeholder="Username" /><br />
          <Field name="password" component={renderInput} type="password" placeholder="Password" /><br />
          <br />
          { isFetching ? (<p>Loading ...</p>) : (<button type="submit">LOGIN</button>) }
        </form>
      </div>
    );
  }
}

LoginPage = reduxForm({
  form: 'Login'
})(LoginPage);

LoginPage = connect(
  state => ({
    isFetching: state.isFetching,
    user: state.user
  }),
  (dispatch, ownProps) => ({
    onSubmit: values => {
      dispatch(actionTryLogin(values));
    }
  })
)(LoginPage);

export default LoginPage;

Dikarenakan reduxForm menggunakan Field maka kita perlu membungkus input form ke komponen ini dan fungsi yang digunakan adalah fungsi renderInput(). Dibagian bawah class kita menggunakan beberapa fungsi untuk dapat menggunakan reduxForm dan juga menggunakan connect dari react-redux untuk menghubungkan komponen dengan state. Fungsi connect memiliki dua parameter yaitu map state to props dan map dispatch to props. Dengan dua parameter ini kita bisa mendelegasikan state dan dispatch ke komponen sebagai props.

Action actionTryLogin() di file actions.js akan kita ubah agar dapat menangani proses post ke back end dan kemudian menerima data dari server untuk diset ke state. Berikut ini kode fungsi actionTryLogin():

...
export function actionTryLogin(values) {
  return dispatch => {
    dispatch(setFetching(true));
    fetch('http://localhost:8000/api/login', {
      body: JSON.stringify(values),
      method: 'POST',
    })
    .then(response => response.json())
    .then(data => {
      const user = data.data;
      if (user !== null) {
        dispatch(setUser(user));
        localStorage.setItem("user", JSON.stringify(user));
      }
      dispatch(setFetching(false));
    })
    .catch(err => {
      dispatch(setFetching(false));
    });
  };
}

Untuk melakukan request ke server bisa menggunakan beragam cara antara lain menggunakan fetch seperti kode diatas. Anda bisa menggunakan axios atau modul lain jika Anda merasa lebih nyaman menggunakan modul tersebut.

Jika server membalas dengan object kita akan mengeset nilainya ke state dengan menggunakan action __SET_USER dan sekaligus kita juga menyimpan data user ke localStorage__ agar ketika browser dibuka ulang user tidak perlu melakukan login kembali.

Laravel

Kita menggunakan salah framework yang cukup terkenal yaitu Laravel dan kita akan membuat project baru dengan menggunakan perintah berikut:

laravel new webapi
cd webapi

Untuk menjalankan servernya kita gunakan perintah:

php artisan serve

Tabel Admins

Untuk database terserah Anda namun pastikan bahwa pengaturan di file .env sudah benar. Kita membutuhkan sebuah tabel yang berisi data user admin dengan struktur tabel sebagai berikut:

Nama FieldTipeKeyKeterangan
idtinyint(3)Primary (auto increment)id pengguna
usernamevarchar(30)username untuk pengguna
passwordvarchar(40)password dengan md5
namavarchar(60)nama dari pengguna

Beri nama tabel tersebut dengan nama admins dan masukkan insert kan data baru dengan untuk Administrator dengan password "password".

FieldValue
id1
usernameadmin
password5f4dcc3b5aa765d61d8327deb882cf99
namaAdministrator

Model

Kita buat model baru dari tabel admins diatas dengan perintah:

php artisan make:model Admin

dan selanjutnya ubah file app/Admin.php dengan menambahkan properti timestamps dengan nilai false.

<?php
...
class Admin extends Model
{
  public $timestamps = false;
}

Router + Controller

Aplikasi React yang kita buat sebelumnya mengirimkan request ke http://localhost:8000/api/login sehingga kita perlu menambahkan router di file routes/api.php dengan route seperti berikut:

Route::post('/login', 'AdminLoginController@login');

Kita belum membuat controller untuk menangani router tersebut, karena itu kita perlu membaut controller baru dengan menggunakan perintah:

php artisan make:controller AdminLoginController

Kemudian kita edit file app/Http/Controllers/AdminLoginController.php menjadi seperti berikut:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Admin;

class AdminLoginController extends Controller
{
  public function login(Request $request) 
  {
    header("Access-Control-Allow-Origin: *");
    $input = $request->json()->all();
    
    $result = Admin::select('id', 'nama')
    ->where('username', $input['username'])
    ->where('password', md5($input['password']))
    ->get();
    if ( ! isset($result[0])) return ['data' => null];
    return ['data' => $result[0]];
  }
}

Jika sukses maka server akan memberikan respon json dengan bentuk:

{data: { id: 1, nama: "Administrator" }}

namun jika gagal akan memberikan respon data dengan nilai null.

React Redirect

Seharusnya setelah tombol ditekan, request ke server dibalas dengan object dari user yang berhasil login maka halaman berpindah ke Beranda. Karena itu kita perlu menambahkan komponen Redirect di fungsi render komponen LoginPage seperti berikut:

...
  render() {
    const isFetching = this.props.isFetching;
    const user = this.props.user;
    if (user.id) {
      return (<Redirect to="/home" />);
    }
    
    return (
      <div>
        <h1>LOGIN PENGGUNA</h1>
        <form onSubmit={this.props.handleSubmit}>
          ...
        </form>
      </div>
    ); 
  }

Bagian `if (user.id) return (<Redirect...` lah yang akan melakukan tugas memindahkan user ke halaman beranda. Sekarang kita coba menggunakan username admin dengan password password dan jika berhasil maka seharusnya Anda sudah melihat halaman Beranda.

Autologin

Seharusnya kita ketika browser ditutup kita tidak perlu lagi melakukan login dan aplikasi akan langsung menuju halaman beranda. Karena kita sudah menggunakan localStorage maka di LoginPage kita bisa mengecek nilai di localStorage apakah ada data user atau tidak. Jika ada maka kita set ke state dan otomatis pengguna akan di redirect ke halaman beranda.

Cara termudah adalah menambahkan pengecekan di dalam fungsi componentWillMount() di LoginPage. Pastikan URL Anda sedang mengakses LoginPage (default path) dan bukan HomePage (/home).

...
class LoginPage extends Component {
  componentWillMount() {
    this.props.checkUser();
  }
...
LoginPage = connect(
  ...
  (dispatch, ownProps) => ({
    onSubmit: values => {
      dispatch(actionTryLogin(values));
    },
    checkUser: () => {
      const user = localStorage.getItem('user');
      if (user !== null) dispatch(setUser(JSON.parse(user)));
    }
  })
)(LoginPage);
...

Kita membuat fungsi checkUser dalam fungsi connect agar bisa menggunakan dispatch yang menggunakan action setUser. Dengan begini maka aplikasi akan mengecek apakah ada data user di localStorage, jika ada maka pengguna akan di redirect langsung ke beranda.

Logout

Kita perlu mengubah komponen HomePage dengan menambahkan link logout, redirect dan connect. Secara umum kode yang digunakan untuk logout adalah sebagai berikut:

import React, { Component } from 'react';
import { Redirect } from 'react-router-dom';
import { connect } from 'react-redux';
import { deleteUser } from '../actions';

class HomePage extends Component {
  
  render() {
    const user = this.props.user;
    if ( ! user.id) {
      return (<Redirect to="/" />);
    }
    
    return (
      <div>
        <h1>BERANDA</h1>
        <p><a href="" onClick={() => this.props.logout()}>LOGOUT</a></p>
      </div>
    );
  }
}

HomePage = connect(
  state => ({
    user: state.user
  }),
  (dispatch, ownProps) => ({
    logout: () => {
      localStorage.clear();
      dispatch(deleteUser())
    }
  })
)(HomePage);

export default HomePage;

Maka selesailah aplikasi sederhana implementasi login menggunakan React + Redux dan Laravel REST API. Terima kasih & Happy coding :)

Artikel user, import, react, return, login, action, loginpage, data, halaman, redux, const, export, komponen, default, fungsi