Web APIにはRESTやGraghQL、gRPCがあるけどどう使い分ければ良いだろう?
Web APIとして広く知られているのはRESTですが、GraphQLやgRPCなどの案件も見かけるようになりました
ですが、これまでRESTしか使ってこなかったエンジニアにとってどのように使い分ければ良いのか分かりません…
そこでフロントエンドにReact、バックエンドにDjangoを採用した環境下でREST、GraphQL、gRPCの実装方法と使い分けについて解説していきます
初回はRESTの実装方法についてです
- Visual Studio Code:1.76.0
- Docker Desktop
ReactプロジェクトはCreate React Appで作成しており、TypeScriptテンプレートを使用しています
詳しくはこちらの記事を参照してください
DjangoのREST framework
まずはDjangoのプロジェクト側で作業します
次のコマンドでDjangoにREST frameworkをインストールします
pip install djangorestframework
プロジェクトにアプリケーションを追加します。今回はアプリケーション名をapiとしました
python manage.py startapp api
settings.pyに先ほど追加したREST frameworkとアプリケーションの登録を行います
自動生成されるConfigクラスの名前はapps.pyを確認してください
INSTALLED_APPS = [
...
'rest_framework',
'api.apps.ApiConfig', # アプリケーション名によってxxx.apps.XxxConfigとなる
]
models.pyにこのアプリケーションで使用するModel(テーブル)を追加します
# Create your models here.
class Todo(models.Model):
task = models.CharField(verbose_name='タスク', max_length=180)
timestamp = models.DateTimeField(
verbose_name='タイムスタンプ', auto_now_add=False)
completed = models.BooleanField(verbose_name='完了済み')
class Meta:
verbose_name_plural = 'Todo'
作成したModelをJSONなどのAPIに適した形式に変換するためにserializers.pyファイルを作成します
from rest_framework import serializers
from .models import Todo
class TodoSerializer(serializers.ModelSerializer):
class Meta:
model = Todo
fields = ('task', 'timestamp', 'completed')
views.pyにModelからデータを取得して返却するAPIViewを作成します
from .serializers import TodoSerializer
from .models import Todo
from rest_framework import generics
# Create your views here.
class TodoAPIView(generics.ListAPIView):
queryset = Todo.objects.all()
serializer_class = TodoSerializer
APIのエンドポイントを設定するためにurls.pyファイルを作成します
from django.urls import path
from .views import TodoAPIView
urlpatterns = [
path('todo', TodoAPIView.as_view()),
]
urls.pyにルーティングがプロジェクトのurls→作成したアプリケーションのurlsになるように設定します
from django.urls import path, include
urlpatterns = [
...
path("api/", include("api.urls")), # アプリケーション名によってxxx.urlsとなる
]
新しくテーブルを作成したのでマイグレーションを実行します
python manage.py makemigrations
python manage.py migrate
admin.pyに追加したテーブルが管理画面で使えるように設定します
from .models import Todo
# Register your models here.
admin.site.register(Todo)
サーバを起動してデータ登録を行います
python manage.py runserver
管理画面から作成したTodoの追加を選択します
React側で表示するデータを2件ほど登録します
Reactのaxios
続いてReactのプロジェクト側で作業をします
次のコマンドで非同期でAPI通信ができるようにaxiosをインストールします
npm install axios
Djangoで作成したAPIから取得した結果をテーブルに出力するようにします
const axiosInstance = axios.create({
baseURL: 'http://localhost:8000',
headers: {
'Content-Type': 'application/json;charset=utf-8',
},
});
const [tableData, setTableData] = useState<Todo[]>([]);
useEffect(() => {
axiosInstance.get('/api/todo').then((response) => {
setTableData(response.data);
});
}, []);
。全ソースはまとめを参照してください
- useStateとは?
-
useStateとはレンダリングに影響するHookで、現在の状態とそれを更新する関数の2つを配列で返します。初期値が引数となり、setter関数が呼び出されるたびに再レンダリングのトリガーになります
- useEffectとは?
-
useEffectはレンダリングに影響しないHookで副作用(side-effect)を管理するために使用されます。レンダリングの外部でコンポーネントに影響を与える操作としてAPI通信やタイマー設定などがあります
TypeScriptでは型を指定しないとエラーになるためAPIの返却値であるTodoを定義します
export interface Todo {
task: string;
timestamp: string;
completed: bool;
}
DJangoが起動している状態でReactも起動します
npm start
VSCodeはDjangoで使用しているので別のコンソール(Powershellなど)を立ち上げればReactも起動できますよ
CORSエラーになる?
正常に通信ができた場合でもCORSエラーが発生し、画面に結果が表示されないと思います
Django側で特定のIPに対してCORSを許可するようにするため、次のパッケージをインストールします
pip install django-cors-headers
settings.pyにアプリケーションとミドルウェアの登録、CORSエラーとしないIPアドレスを追加します
INSTALLED_APPS = [
...
"corsheaders",
]
MIDDLEWARE = [
...
"corsheaders.middleware.CorsMiddleware"
]
# React側のIPアドレスを追加
CORS_ORIGIN_WHITELIST = [
'http://localhost:3000',
'http://127.0.0.1:3000',
]
Djangoサーバーを再起動すれば正常に通信ができるようになっています
まとめ
今回作成したReact側のソースです
App.tsx
import { Card, CardContent, Container } from '@mui/material';
import CssBaseline from '@mui/material/CssBaseline';
import { createTheme, ThemeProvider } from '@mui/material/styles';
import * as React from 'react';
import CustomTable from './components/CustomTable';
function App() {
const theme = createTheme({});
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<Container fixed>
<Card variant="elevation" elevation={0}>
<CardContent>
<CustomTable />
</CardContent>
</Card>
</Container>
</ThemeProvider>
);
}
export default App;
CustomTable.tsx
import Paper from '@mui/material/Paper';
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
import TableCell, { tableCellClasses } from '@mui/material/TableCell';
import TableContainer from '@mui/material/TableContainer';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import { styled } from '@mui/material/styles';
import axios from 'axios';
import React, { useState, useEffect } from 'react';
import { Todo } from '../global';
function CustomTable() {
const axiosInstance = axios.create({
baseURL: 'http://localhost:8000',
headers: {
'Content-Type': 'application/json;charset=utf-8',
},
});
const [tableData, setTableData] = useState<Todo[]>([]);
useEffect(() => {
axiosInstance.get('/api/todo').then((response) => {
setTableData(response.data);
});
}, []);
const StyledTableCell = styled(TableCell)(({ theme }) => ({
[`&.${tableCellClasses.head}`]: {
backgroundColor: theme.palette.common.black,
color: theme.palette.common.white,
},
[`&.${tableCellClasses.body}`]: {
fontSize: 14,
},
}));
return (
<TableContainer component={Paper}>
<Table sx={{ minWidth: 650 }} aria-label="simple table">
<TableHead>
<TableRow>
<StyledTableCell>Task</StyledTableCell>
<StyledTableCell>Timestamp</StyledTableCell>
<StyledTableCell>Completed</StyledTableCell>
</TableRow>
</TableHead>
<TableBody>
{tableData.map((row) => (
<TableRow key={row.task}>
<TableCell>{row.task}</TableCell>
<TableCell>{row.timestamp}</TableCell>
<TableCell>{row.completed.toString()}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
);
}
export default CustomTable;