เชื่อมต่อ React กับ REST API
RESTful หรือ REST คือ
Representational state transfer หรือ REST คือ การสร้าง RESTful Web services ชนิดหนึ่งที่ใช้สื่อสารกันบน Internet ใช้หลักการแบบ stateless คือไม่มี session ซึ่งต่างจาก Web services แบบอื่นเช่น WSDL และ SOAP การทำงานของ RESTful Web services จะอาศัย URI/URL ของ request เพื่อค้นหาและประมวลผลแล้วตอบกลับไปในรูป XML, HTML, JSON โดย response ที่ตอบกลับจะเป็นการยืนยันผลของคำสั่งที่ส่งมา และสามารถพัฒนาด้วยภาษา programming ได้หลากหลาย คำสั่งก็จะมีตาม HTTP verbs ซึ่งก็คือ
- GET ทำการดึงข้อมูลภายใน URI ที่กำหนด
- POST สำหรับสร้างข้อมูล
- PUT ใช้แก้ไขข้อมูล
- DELETE สำหรับลบข้อมูล
ข้อกำหนดเบื้องต้น
ข้อกำหนดสำหรับบทความนี้คือ คุณต้องปฏิบัติตามบทความ สร้าง Front-End ด้วย React และ สร้าง Go Back-End REST API มาก่อน
เชื่อมต่อกับ REST API
การเชื่อมต่อระหว่าง react กับ REST API ต้องเปิดการทำงานของ Go Back-End REST API ขึ้นมาเพื่อรอการเชื่อมต่อ
เปิดโปรเจค สร้าง Front-End ด้วย React ขึ้นมา แล้วแก้ไขไฟล์ Movies.js ตามโค้ดด้านล่าง
import React, { Component, Fragment } from "react";
import { Link } from "react-router-dom";
export default class Movies extends Component {
state = {
movies: [],
isLoaded: false,
};
componentDidMount() {
fetch("http://localhost:4000/v1/movies")
.then((response) => response.json())
.then((json) => {
this.setState({
movies: json.movies,
isLoaded: true,
});
});
}
render() {
const { movies, isLoaded } = this.state;
if (!isLoaded) {
return <p>Loading...</p>;
} else {
return (
<Fragment>
<h2>Choose a movie</h2>
<ul>
{movies.map((m) => (
<li key={m.id}>
<Link to={`/movies/${m.id}`}>{m.title}</Link>
</li>
))}
</ul>
</Fragment>
);
}
}
}
ทดสอบการทำงาน npm start
ที่เว็บเบราเซอร์ ไปที่เมนู Movies
คลิกขวา -> Inspect
เลือก Console และ XHR แล้ว refresh
XMLHttpRequest (XHR) เป็นเอพีไอที่สามารถเรียกใช้ได้จาก จาวาสคริปต์ และภาษาสคริปต์อื่นๆ ในการแลกเปลี่ยน และปรับรูปแบบ XML จากเว็บเซิร์ฟเวอร์ โดยใช้ HTTP ซึ่งสร้างการเชื่อมต่อระหว่างเว็บเบราว์เซอร์ (Client-Side) กับ เว็บเซิร์ฟเวอร์ (Server-Side)
จะมีข้อความ XHR GET http://localhost:4000/v1/movies ขึ้นมา แสดงว่าเชื่อมต่อกับ Go Back-End REST API ได้แล้ว
คลิกที่ปุ่มสามเหลี่ยมด้านหน้า
จะแสดงข้อมูลการเชื่อมต่อ
เมื่อคลิกที่ Response จะแสดงข้อมูล JSON จาก Go Back-End REST API
Checking for errors
ตรวจสอบข้อผิดพลาด โดยแก้ไขไฟล์ Movies.js ตามโค้ดด้านล่าง
import React, { Component, Fragment } from "react";
import { Link } from "react-router-dom";
export default class Movies extends Component {
state = {
movies: [],
isLoaded: false,
error: null,
};
componentDidMount() {
fetch("http://localhost:4000/v1/movies")
.then((response) => {
console.log("Status code is", response.status);
if (response.status !== "200") {
let err = Error;
err.message = "Invalid response code: " + response.status;
this.setState({error: err});
}
return response.json();
})
.then((json) => {
this.setState({
movies: json.movies,
isLoaded: true,
},
(error) => {
this.setState({
isLoaded: true,
error
});
}
);
});
}
render() {
const { movies, isLoaded, error } = this.state;
if (error) {
return <div>Error: {error.message}</div>
} else if (!isLoaded) {
return <p>Loading...</p>;
} else {
return (
<Fragment>
<h2>Choose a movie</h2>
<ul>
{movies.map((m) => (
<li key={m.id}>
<Link to={`/movies/${m.id}`}>{m.title}</Link>
</li>
))}
</ul>
</Fragment>
);
}
}
}
ทดสอบ แก้ไขชั่วคราว จาก http://localhost:4000/v1/movies เป็น http://localhost:4000/v1/moviesxx
ที่เว็บเบราเซอร์ ไปที่เมนู Movies จะแสดงข้อผิดพลาด
Displaying one movie
แสดง movie 1 รายการ โดย แก้ไขไฟล์ OneMovie.js ตามโค้ดด้านล่าง
import React, { Component, Fragment } from "react";
export default class OneMovie extends Component {
state = { movie: {}, isLoaded: false, error: null };
componentDidMount() {
fetch("http://localhost:4000/v1/movie/" + this.props.match.params.id)
.then((response) => {
if (response.status !== "200") {
let err = Error;
err.message = "Invalid response code: " + response.status;
this.setState({ error: err });
}
return response.json();
})
.then((json) => {
this.setState(
{
movie: json.movie,
isLoaded: true,
},
(error) => {
this.setState({
isLoaded: true,
error,
});
}
);
});
}
render() {
const { movie, isLoaded, error } = this.state;
if (movie.genres) {
movie.genres = Object.values(movie.genres);
} else {
movie.genres = [];
}
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <p>Loading...</p>;
} else {
return (
<Fragment>
<h2>
Movie: {movie.title} ({movie.year})
</h2>
<div className="float-start">
<small>Rating: {movie.mpaa_rating}</small>
</div>
<div className="float-end">
{movie.genres.map((m, index) =>(
<span className="badge bg-secondary me-1" key={index}>
{m}
</span>
))}
</div>
<div className="clearfix"></div>
<hr />
<table className="table table-compact table-striped">
<thead></thead>
<tbody>
<tr>
<td>
<strong>Title:</strong>
</td>
<td>{movie.title}</td>
</tr>
<tr>
<td><strong>Description:</strong></td>
<td>{movie.description}</td>
</tr>
<tr>
<td>
<strong>Run time:</strong>
</td>
<td>{movie.runtime} minutes</td>
</tr>
</tbody>
</table>
</Fragment>
);
}
}
}
ที่เว็บเบราเซอร์ ไปที่เมนู Movies
ทดลองคลิกที่รายการ Choose a movie จะแสดงรายละเอียดของ movie แต่ละรายการ
Movies by Genre
แสดงประเภทของ Movie
ภายในโฟลเดอร์ components สร้างไฟล์คอมโพเนนท์ ชื่อ Genres.js มีโค้ดดังนี้
import React, { Component, Fragment } from 'react'
import { Link } from 'react-router-dom';
export default class Genres extends Component {
state = {
genres: [],
isLoaded: false,
error: null,
}
componentDidMount() {
fetch("http://localhost:4000/v1/genres")
.then((response) => {
if (response.status !== "200") {
let err = Error;
err.message = "Invalid response code: " + response.status;
this.setState({error: err});
}
return response.json();
})
.then((json) => {
this.setState({
movies: json.genres,
isLoaded: true,
},
(error) => {
this.setState({
isLoaded: true,
error
});
}
);
});
}
render() {
const { genres, isLoaded, error } = this.state;
return (
<Fragment>
<h2>Genres</h2>
<ul>
{genres.map((m) => (
<li key={m.id}>
<Link to={`/genre/${m.id}`}>{m.genre_name}</Link>
</li>
))}
</ul>
</Fragment>
)
}
}
ที่ไฟล์ App.js เขียนโค้ด ดังนี้
import React, { Component, Fragment } from 'react';
import {BrowserRouter as Router, Switch, Route, Link, useParams, useRouteMatch} from 'react-router-dom';
import Movies from './components/Movies';
import Admin from './components/Admin';
import Home from './components/Home';
import OneMovie from './components/OneMovie';
import Genres from './components/Genres';
export default function App() {
return (
<Router>
<div className="container">
<div className="row">
<h1 className="mt-3">
Go Watch a Movie!
</h1>
<hr className="mb-3"></hr>
</div>
<div className="row">
<div className="col-md-2">
<nav>
<ul className="list-group">
<li className="list-group-item">
<Link to="/">Home</Link>
</li>
<li className="list-group-item">
<Link to="/movies">Movies</Link>
</li>
<li className="list-group-item">
<Link to="/genres">Genres</Link>
</li>
<li className="list-group-item">
<Link to="/admin">Manage Catalogue</Link>
</li>
</ul>
</nav>
</div>
<div className="col-md-10">
<Switch>
<Route path="/movies/:id" component={OneMovie} />
<Route path="/movies">
<Movies />
</Route>
<Route exact path="/genres">
<Genres />
</Route>
<Route path="/admin">
<Admin />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
</div>
</div>
</div>
</Router>
);
}
ที่เว็บเบราเซอร์ ไปที่เมนู Genres จะมี่ข้อความ Genres แต่ยังไม่มี การแสดงประเภทของ Movie
Genres from back end
นำรายการประเภทของ Movie จาก Back-End มาแสดง
โดยกลับไปที่โปรเจ็ค Go Back-End REST API ที่ไฟล์ movies-db.go เขียนโค้ดดังนี้
package models
import (
"context"
"database/sql"
"time"
)
type DBModel struct {
DB *sql.DB
}
// Get returns one movie and error, if any
func (m *DBModel) Get(id int) (*Movie, error) {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
query := `select id, title, description, year, release_date, rating, runtime, mpaa_rating,
created_at, updated_at from movies where id = $1
`
row := m.DB.QueryRowContext(ctx, query, id)
var movie Movie
err := row.Scan(
&movie.ID,
&movie.Title,
&movie.Description,
&movie.Year,
&movie.ReleaseDate,
&movie.Rating,
&movie.Runtime,
&movie.MPAARating,
&movie.CreatedAt,
&movie.UpdatedAt,
)
if err != nil {
return nil, err
}
// get genres, if any
query = `select
mg.id, mg.movie_id, mg.genre_id, g.genre_name
from
movies_genres mg
left join genres g on (g.id = mg.genre_id)
where
mg.movie_id = $1
`
rows, _ := m.DB.QueryContext(ctx, query, id)
defer rows.Close()
genres := make(map[int]string)
for rows.Next() {
var mg MovieGenre
err := rows.Scan(
&mg.ID,
&mg.MovieID,
&mg.GenreID,
&mg.Genre.GenreName,
)
if err != nil {
return nil, err
}
genres[mg.ID] = mg.Genre.GenreName
}
movie.MovieGenre = genres
return &movie, nil
}
// All returns all movies and error, if any
func (m *DBModel) All() ([]*Movie, error) {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
query := `select id, title, description, year, release_date, rating, runtime, mpaa_rating,
created_at, updated_at from movies order by title
`
rows, err := m.DB.QueryContext(ctx, query)
if err != nil {
return nil, err
}
defer rows.Close()
var movies []*Movie
for rows.Next() {
var movie Movie
err := rows.Scan(
&movie.ID,
&movie.Title,
&movie.Description,
&movie.Year,
&movie.ReleaseDate,
&movie.Rating,
&movie.Runtime,
&movie.MPAARating,
&movie.CreatedAt,
&movie.UpdatedAt,
)
if err != nil {
return nil, err
}
// get genres, if any
genreQuery := `select
mg.id, mg.movie_id, mg.genre_id, g.genre_name
from
movies_genres mg
left join genres g on (g.id = mg.genre_id)
where
mg.movie_id = $1
`
genreRows, _ := m.DB.QueryContext(ctx, genreQuery, movie.ID)
genres := make(map[int]string)
for genreRows.Next() {
var mg MovieGenre
err := genreRows.Scan(
&mg.ID,
&mg.MovieID,
&mg.GenreID,
&mg.Genre.GenreName,
)
if err != nil {
return nil, err
}
genres[mg.ID] = mg.Genre.GenreName
}
genreRows.Close()
movie.MovieGenre = genres
movies = append(movies, &movie)
}
return movies, nil
}
func (m *DBModel) GenresAll() ([]*Genre, error) {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
query := `select id, genre_name, created_at, updated_at from genres order by genre_name`
rows, err := m.DB.QueryContext(ctx, query)
if err != nil {
return nil, err
}
defer rows.Close()
var genres []*Genre
for rows.Next() {
var g Genre
err := rows.Scan(
&g.ID,
&g.GenreName,
&g.CreatedAt,
&g.UpdatedAt,
)
if err != nil {
return nil, err
}
genres = append(genres, &g)
}
return genres, nil
}
ที่ไฟล์ movie-handlers.go เขียนโค้ดดังนี้
package main
import (
"errors"
"net/http"
"strconv"
"github.com/julienschmidt/httprouter"
)
func (app *application) getOneMovie(w http.ResponseWriter, r *http.Request) {
params := httprouter.ParamsFromContext(r.Context())
id, err := strconv.Atoi(params.ByName("id"))
if err != nil {
app.logger.Print(errors.New("invalid id parameter"))
app.errorJSON(w, err)
return
}
movie, err := app.models.DB.Get(id)
err = app.writeJSON(w, http.StatusOK, movie, "movie")
if err != nil {
app.errorJSON(w, err)
return
}
}
func (app *application) getAllMovies(w http.ResponseWriter, r *http.Request) {
movies, err := app.models.DB.All()
if err != nil {
app.errorJSON(w, err)
return
}
err = app.writeJSON(w, http.StatusOK, movies, "movies")
if err != nil {
app.errorJSON(w, err)
return
}
}
func (app *application) getAllGenres(w http.ResponseWriter, r *http.Request) {
genres, err := app.models.DB.GenresAll()
if err != nil {
app.errorJSON(w, err)
return
}
err = app.writeJSON(w, http.StatusOK, genres, "genres")
if err != nil {
app.errorJSON(w, err)
return
}
}
func (app *application) deleteMovie(w http.ResponseWriter, r *http.Request) {
}
func (app *application) insertMovie(w http.ResponseWriter, r *http.Request) {
}
func (app *application) updateMovie(w http.ResponseWriter, r *http.Request) {
}
func (app *application) searchMovies(w http.ResponseWriter, r *http.Request) {
}
ที่ไฟล์ routes.go เขียนโค้ดดังนี้
package main
import (
"net/http"
"github.com/julienschmidt/httprouter"
)
func (app *application) routes() http.Handler {
router := httprouter.New()
router.HandlerFunc(http.MethodGet, "/status", app.statusHandler)
router.HandlerFunc(http.MethodGet, "/v1/movie/:id", app.getOneMovie)
router.HandlerFunc(http.MethodGet, "/v1/movies", app.getAllMovies)
router.HandlerFunc(http.MethodGet, "/v1/genres", app.getAllGenres)
return app.enableCORS(router)
}
แล้วเปิดการทำงานของ Go Back-End REST API ด้วยคำสั่ง go run ./cmd/api/ .
ที่เว็บเบราว์เซอร์ เปิด url > http://localhost:4000/v1/genres จะแสดงรายการประเภทของ Movie ในรูปแบบ JSON
Displaying the list of Genres
แสดงรายการประเภทของ Movie ที่ Front-End
โดยที่ Back-End ไฟล์ models.go แก้ไขดังนี้
package models
import (
"database/sql"
"time"
)
// Models is the wrapper for database
type Models struct {
DB DBModel
}
// NewModels returns models with db pool
func NewModels(db *sql.DB) Models {
return Models{
DB: DBModel{DB: db},
}
}
// Movie is the type for movies
type Movie struct {
ID int `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Year int `json:"year"`
ReleaseDate time.Time `json:"release_date"`
Runtime int `json:"runtime"`
Rating int `json:"rating"`
MPAARating string `json:"mpaa_rating"`
CreatedAt time.Time `json:"-"`
UpdatedAt time.Time `json:"-"`
MovieGenre map[int]string `json:"genres"`
}
// Genre is the type for genre
type Genre struct {
ID int `json:"id"`
GenreName string `json:"genre_name"`
CreatedAt time.Time `json:"-"`
UpdatedAt time.Time `json:"-"`
}
// MovieGenre is the type for movie genre
type MovieGenre struct {
ID int `json:"-"`
MovieID int `json:"-"`
GenreID int `json:"-"`
Genre Genre `json:"genre"`
CreatedAt time.Time `json:"-"`
UpdatedAt time.Time `json:"-"`
}
ที่เว็บเบราว์เซอร์ เปิด url > http://localhost:4000/v1/genres
Front-End ไฟล์ Genres.js แก้ไขโค้ดดังนี้
import React, { Component, Fragment } from "react";
import { Link } from "react-router-dom";
export default class Genres extends Component {
state = {
genres: [],
isLoaded: false,
error: null,
};
componentDidMount() {
fetch("http://localhost:4000/v1/genres")
.then((response) => {
if (response.status !== "200") {
let err = Error;
err.message = "Invalid response code: " + response.status;
this.setState({ error: err });
}
return response.json();
})
.then((json) => {
this.setState(
{
genres: json.genres,
isLoaded: true,
},
(error) => {
this.setState({
isLoaded: true,
error,
});
}
);
});
}
render() {
const { genres, isLoaded, error } = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <p>Loading...</p>;
} else {
return (
<Fragment>
<h2>Genres</h2>
<ul>
{genres.map((m) => (
<li key={m.id}>
<Link to={`/genre/${m.id}`}>{m.genre_name}</Link>
</li>
))}
</ul>
</Fragment>
);
}
}
}
ที่เว็บเบราเซอร์ ไปที่เมนู Genres http://localhost:3000/genres จะแสดงรายการ Movie ตามประเภทเพิ่มเข้ามา
Movies by Genre
แสดง รายละเอียด movie และ ประเภท
Back-End ไฟล์ movies-db.go แก้ไขดังนี้
package models
import (
"context"
"database/sql"
"fmt"
"time"
)
type DBModel struct {
DB *sql.DB
}
// Get returns one movie and error, if any
func (m *DBModel) Get(id int) (*Movie, error) {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
query := `select id, title, description, year, release_date, rating, runtime, mpaa_rating,
created_at, updated_at from movies where id = $1
`
row := m.DB.QueryRowContext(ctx, query, id)
var movie Movie
err := row.Scan(
&movie.ID,
&movie.Title,
&movie.Description,
&movie.Year,
&movie.ReleaseDate,
&movie.Rating,
&movie.Runtime,
&movie.MPAARating,
&movie.CreatedAt,
&movie.UpdatedAt,
)
if err != nil {
return nil, err
}
// get genres, if any
query = `select
mg.id, mg.movie_id, mg.genre_id, g.genre_name
from
movies_genres mg
left join genres g on (g.id = mg.genre_id)
where
mg.movie_id = $1
`
rows, _ := m.DB.QueryContext(ctx, query, id)
defer rows.Close()
genres := make(map[int]string)
for rows.Next() {
var mg MovieGenre
err := rows.Scan(
&mg.ID,
&mg.MovieID,
&mg.GenreID,
&mg.Genre.GenreName,
)
if err != nil {
return nil, err
}
genres[mg.ID] = mg.Genre.GenreName
}
movie.MovieGenre = genres
return &movie, nil
}
// All returns all movies and error, if any
func (m *DBModel) All(genre ...int) ([]*Movie, error) {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
where := ""
if len(genre) > 0 {
where = fmt.Sprintf("where id in (select movie_id from movies_genres where genre_id = %d)", genre[0])
}
query := fmt.Sprintf(`select id, title, description, year, release_date, rating, runtime, mpaa_rating,
created_at, updated_at from movies %s order by title`, where)
rows, err := m.DB.QueryContext(ctx, query)
if err != nil {
return nil, err
}
defer rows.Close()
var movies []*Movie
for rows.Next() {
var movie Movie
err := rows.Scan(
&movie.ID,
&movie.Title,
&movie.Description,
&movie.Year,
&movie.ReleaseDate,
&movie.Rating,
&movie.Runtime,
&movie.MPAARating,
&movie.CreatedAt,
&movie.UpdatedAt,
)
if err != nil {
return nil, err
}
// get genres, if any
genreQuery := `select
mg.id, mg.movie_id, mg.genre_id, g.genre_name
from
movies_genres mg
left join genres g on (g.id = mg.genre_id)
where
mg.movie_id = $1
`
genreRows, _ := m.DB.QueryContext(ctx, genreQuery, movie.ID)
genres := make(map[int]string)
for genreRows.Next() {
var mg MovieGenre
err := genreRows.Scan(
&mg.ID,
&mg.MovieID,
&mg.GenreID,
&mg.Genre.GenreName,
)
if err != nil {
return nil, err
}
genres[mg.ID] = mg.Genre.GenreName
}
genreRows.Close()
movie.MovieGenre = genres
movies = append(movies, &movie)
}
return movies, nil
}
func (m *DBModel) GenresAll() ([]*Genre, error) {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
query := `select id, genre_name, created_at, updated_at from genres order by genre_name`
rows, err := m.DB.QueryContext(ctx, query)
if err != nil {
return nil, err
}
defer rows.Close()
var genres []*Genre
for rows.Next() {
var g Genre
err := rows.Scan(
&g.ID,
&g.GenreName,
&g.CreatedAt,
&g.UpdatedAt,
)
if err != nil {
return nil, err
}
genres = append(genres, &g)
}
return genres, nil
}
ไฟล์ movie-handlers.go แก้ไขดังนี้
package main
import (
"errors"
"net/http"
"strconv"
"github.com/julienschmidt/httprouter"
)
func (app *application) getOneMovie(w http.ResponseWriter, r *http.Request) {
params := httprouter.ParamsFromContext(r.Context())
id, err := strconv.Atoi(params.ByName("id"))
if err != nil {
app.logger.Print(errors.New("invalid id parameter"))
app.errorJSON(w, err)
return
}
movie, err := app.models.DB.Get(id)
err = app.writeJSON(w, http.StatusOK, movie, "movie")
if err != nil {
app.errorJSON(w, err)
return
}
}
func (app *application) getAllMovies(w http.ResponseWriter, r *http.Request) {
movies, err := app.models.DB.All()
if err != nil {
app.errorJSON(w, err)
return
}
err = app.writeJSON(w, http.StatusOK, movies, "movies")
if err != nil {
app.errorJSON(w, err)
return
}
}
func (app *application) getAllGenres(w http.ResponseWriter, r *http.Request) {
genres, err := app.models.DB.GenresAll()
if err != nil {
app.errorJSON(w, err)
return
}
err = app.writeJSON(w, http.StatusOK, genres, "genres")
if err != nil {
app.errorJSON(w, err)
return
}
}
func (app *application) getAllMoviesByGenre(w http.ResponseWriter, r *http.Request) {
params := httprouter.ParamsFromContext(r.Context())
genreID, err := strconv.Atoi(params.ByName("genre_id"))
if err != nil {
app.errorJSON(w, err)
return
}
movies, err := app.models.DB.All(genreID)
if err != nil {
app.errorJSON(w, err)
return
}
err = app.writeJSON(w, http.StatusOK, movies, "movies")
if err != nil {
app.errorJSON(w, err)
return
}
}
func (app *application) deleteMovie(w http.ResponseWriter, r *http.Request) {
}
func (app *application) insertMovie(w http.ResponseWriter, r *http.Request) {
}
func (app *application) updateMovie(w http.ResponseWriter, r *http.Request) {
}
func (app *application) searchMovies(w http.ResponseWriter, r *http.Request) {
}
ไฟล์ routes.go แก้ไขดังนี้
package main
import (
"net/http"
"github.com/julienschmidt/httprouter"
)
func (app *application) routes() http.Handler {
router := httprouter.New()
router.HandlerFunc(http.MethodGet, "/status", app.statusHandler)
router.HandlerFunc(http.MethodGet, "/v1/movie/:id", app.getOneMovie)
router.HandlerFunc(http.MethodGet, "/v1/movies", app.getAllMovies)
router.HandlerFunc(http.MethodGet, "/v1/movies/:genre_id", app.getAllMoviesByGenre)
router.HandlerFunc(http.MethodGet, "/v1/genres", app.getAllGenres)
return app.enableCORS(router)
}
ที่เว็บเบราเซอร์ ไปที่เมนู Genres http://localhost:4000/v1/movie/2 จะแสดง รายละเอียด movie และ ประเภท
Displaying movies by Genre
แสดง ประเภท และ เมื่อคลิกไปจะแสดง movie ที่อยู่ในประเภทนี้ และเมื่อคลิกต่อไป จะแสดงรายละเอียด movie
Front-End ไฟล์ App.js แก้ไขโค้ดดังนี้
import React, { Component, Fragment } from 'react';
import {BrowserRouter as Router, Switch, Route, Link, useParams, useRouteMatch} from 'react-router-dom';
import Movies from './components/Movies';
import Admin from './components/Admin';
import Home from './components/Home';
import OneMovie from './components/OneMovie';
import Genres from './components/Genres';
import OneGenre from './components/OneGenre';
export default function App() {
return (
<Router>
<div className="container">
<div className="row">
<h1 className="mt-3">
Go Watch a Movie!
</h1>
<hr className="mb-3"></hr>
</div>
<div className="row">
<div className="col-md-2">
<nav>
<ul className="list-group">
<li className="list-group-item">
<Link to="/">Home</Link>
</li>
<li className="list-group-item">
<Link to="/movies">Movies</Link>
</li>
<li className="list-group-item">
<Link to="/genres">Genres</Link>
</li>
<li className="list-group-item">
<Link to="/admin">Manage Catalogue</Link>
</li>
</ul>
</nav>
</div>
<div className="col-md-10">
<Switch>
<Route path="/movies/:id" component={OneMovie} />
<Route path="/movies">
<Movies />
</Route>
<Route path="/genre/:id" component={OneGenre} />
<Route exact path="/genres">
<Genres />
</Route>
<Route path="/admin">
<Admin />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
</div>
</div>
</div>
</Router>
);
}
ที่โฟลเดอร์ components สร้างไฟล์คอมโพเนนท์ ชื่อ OneGenre.js มีโค้ดดังนี้
import React, { Component, Fragment } from "react";
import { Link } from "react-router-dom";
export default class OneGenre extends Component {
state = {
movies: [],
isLoaded: false,
error: null,
};
componentDidMount() {
fetch("http://localhost:4000/v1/movies/" + this.props.match.params.id)
.then((response) => {
if (response.status !== "200") {
let err = Error;
err.message = "Invalid response code: " + response.status;
this.setState({ error: err });
}
return response.json();
})
.then((json) => {
this.setState(
{
movies: json.movies,
isLoaded: true,
},
(error) => {
this.setState({
isLoaded: true,
error,
});
}
);
});
}
render() {
let { movies, isLoaded, error } = this.state;
if (!movies) {
movies = [];
}
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <p>Loading...</p>;
} else {
return (
<Fragment>
<h2>Genre: </h2>
<div className="list-group">
{movies.map((m) => (
<Link to={`/movies/${m.id}`} className="list-group-item list-group-item-action">{m.title}</Link>
))}
</div>
</Fragment>
);
}
}
}
ที่เว็บเบราเซอร์ ไปที่เมนู Genres http://localhost:3000/genres จะแสดงรายการแต่ละประเภท
ทดสอบ คลิกที่ Drama
คลิกที่ The Shawshank Redemption
Showing Genre name
แสดงประเภท movie หลังข้อความ Genre:
Front-End ไฟล์ OneGenre.js แก้ไขโค้ดดังนี้
import React, { Component, Fragment } from "react";
import { Link } from "react-router-dom";
export default class OneGenre extends Component {
state = {
movies: [],
isLoaded: false,
error: null,
genreName: "",
};
componentDidMount() {
fetch("http://localhost:4000/v1/movies/" + this.props.match.params.id)
.then((response) => {
if (response.status !== "200") {
let err = Error;
err.message = "Invalid response code: " + response.status;
this.setState({ error: err });
}
return response.json();
})
.then((json) => {
this.setState(
{
movies: json.movies,
isLoaded: true,
genreName: this.props.location.genreName,
},
(error) => {
this.setState({
isLoaded: true,
error,
});
}
);
});
}
render() {
let { movies, isLoaded, error, genreName } = this.state;
if (!movies) {
movies = [];
}
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <p>Loading...</p>;
} else {
return (
<Fragment>
<h2>Genre: {genreName}</h2>
<div className="list-group">
{movies.map((m) => (
<Link
key={m.id}
to={`/movies/${m.id}`}
className="list-group-item list-group-item-action"
>
{m.title}
</Link>
))}
</div>
</Fragment>
);
}
}
}
ไฟล์ Genres.js แก้ไขโค้ดดังนี้
import React, { Component, Fragment } from "react";
import { Link } from "react-router-dom";
export default class Genres extends Component {
state = {
genres: [],
isLoaded: false,
error: null,
};
componentDidMount() {
fetch("http://localhost:4000/v1/genres")
.then((response) => {
if (response.status !== "200") {
let err = Error;
err.message = "Invalid response code: " + response.status;
this.setState({ error: err });
}
return response.json();
})
.then((json) => {
this.setState(
{
genres: json.genres,
isLoaded: true,
},
(error) => {
this.setState({
isLoaded: true,
error,
});
}
);
});
}
render() {
const { genres, isLoaded, error } = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <p>Loading...</p>;
} else {
return (
<Fragment>
<h2>Genres</h2>
<ul>
{genres.map((m) => (
<li key={m.id}>
<Link
to={{
pathname: `/genre/${m.id}`,
genreName: m.genre_name,
}}
>
{m.genre_name}
</Link>
</li>
))}
</ul>
</Fragment>
);
}
}
}
ที่เว็บเบราเซอร์ ไปที่เมนู Genres http://localhost:3000/genres จะแสดงรายการแต่ละประเภท ทดสอบ คลิกที่ Drama จะแสดง ประเภท หลัง Genre:
Code clean up
ปรับปรุงโค้ดให้ดียิ่งขึ้น
Front-End ไฟล์ OneGenre.js แก้ไขโค้ดดังนี้
import React, { Component, Fragment } from "react";
import { Link } from "react-router-dom";
export default class OneGenre extends Component {
state = {
movies: [],
isLoaded: false,
error: null,
genreName: "",
};
componentDidMount() {
fetch("http://localhost:4000/v1/movies/" + this.props.match.params.id)
.then((response) => {
if (response.status !== "200") {
let err = Error;
err.message = "Invalid response code: " + response.status;
this.setState({ error: err });
}
return response.json();
})
.then((json) => {
this.setState(
{
movies: json.movies,
isLoaded: true,
genreName: this.props.location.genreName,
},
(error) => {
this.setState({
isLoaded: true,
error,
});
}
);
});
}
render() {
let { movies, isLoaded, error, genreName } = this.state;
if (!movies) {
movies = [];
}
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <p>Loading...</p>;
} else {
return (
<Fragment>
<h2>Genre: {genreName}</h2>
<div className="list-group">
{movies.map((m) => (
<Link
key={m.id}
to={`/movies/${m.id}`}
className="list-group-item list-group-item-action"
>
{m.title}
</Link>
))}
</div>
</Fragment>
);
}
}
}
ไฟล์ Movies.js แก้ไขโค้ดดังนี้
import React, { Component, Fragment } from "react";
import { Link } from "react-router-dom";
export default class Movies extends Component {
state = {
movies: [],
isLoaded: false,
error: null,
};
componentDidMount() {
fetch("http://localhost:4000/v1/movies")
.then((response) => {
if (response.status !== "200") {
let err = Error;
err.message = "Invalid response code: " + response.status;
this.setState({ error: err });
}
return response.json();
})
.then((json) => {
this.setState(
{
movies: json.movies,
isLoaded: true,
},
(error) => {
this.setState({
isLoaded: true,
error,
});
}
);
});
}
render() {
const { movies, isLoaded, error } = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <p>Loading...</p>;
} else {
return (
<Fragment>
<h2>Choose a movie</h2>
<div className="list-group">
{movies.map((m) => (
<Link
key={m.id}
className="list-group-item list-group-item-action"
to={`/movies/${m.id}`}
>
{m.title}
</Link>
))}
</div>
</Fragment>
);
}
}
}
ไฟล์ Genres.js แก้ไขโค้ดดังนี้
import React, { Component, Fragment } from "react";
import { Link } from "react-router-dom";
export default class Genres extends Component {
state = {
genres: [],
isLoaded: false,
error: null,
};
componentDidMount() {
fetch("http://localhost:4000/v1/genres")
.then((response) => {
if (response.status !== "200") {
let err = Error;
err.message = "Invalid response code: " + response.status;
this.setState({ error: err });
}
return response.json();
})
.then((json) => {
this.setState(
{
genres: json.genres,
isLoaded: true,
},
(error) => {
this.setState({
isLoaded: true,
error,
});
}
);
});
}
render() {
const { genres, isLoaded, error } = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <p>Loading...</p>;
} else {
return (
<Fragment>
<h2>Genres</h2>
<div className="list-group">
{genres.map((m) => (
<Link
key={m.id}
className="list-group-item list-group-item-action"
to={{
pathname: `/genre/${m.id}`,
genreName: m.genre_name,
}}
>
{m.genre_name}
</Link>
))}
</div>
</Fragment>
);
}
}
}
ที่เว็บเบราเซอร์ ไปที่เมนู Genres http://localhost:3000/genre
credit : https://www.udemy.com/course/working-with-react-and-go-golang/