【Next.js】AppRouterでお問い合わせフォームを作ってみた!!
はじめに
Next.js 13でリリースされたAppRouterを活用してお問い合わせフォオームをインプット・アウトプットとして作ってみます。
使用技術
以下の物を使用
- Next.js(AppRouter)
- TypeScript
- Chakra UI
- MySQL
- Docker
環境構築
空のフォルダを作成し以下のようなディレクトリ構成にする
.
├── .env
├── .gitignore
├── Dockerfile
├── app
├── docker-compose.yml
├── initdb.d
│ └── create-table.sql
└── mysql
└── my.cnf
Dockerfile
には以下を記述
FROM node:lts-alpine
WORKDIR /app
docker-compose.yml
には以下を記述
version: '3'
services:
app:
container_name: app
build:
context: .
dockerfile: Dockerfile
volumes:
- ./app:/app
command: sh -c "npm run dev"
tty: true
ports:
- 3000:3000
environment:
- DB_HOST=${MYSQL_HOST}
- DB_PORT=${MYSQL_PORT}
- DB_USER=${MYSQL_USER}
- DB_PASSWORD=${MYSQL_PASSWORD}
- DB_DATABASE=${MYSQL_DATABASE}
depends_on:
- mysql
mysql:
container_name: db
image: mysql:latest
restart: always
ports:
- ${MYSQL_PORT}:${MYSQL_PORT}
environment:
- MYSQL_ROOT_PASSWORD=${MYSQL_PASSWORD}
- MYSQL_DATABASE=${MYSQL_DATABASE}
- TZ=${TZ} # タイムゾーン
volumes:
- ./initdb.d:/docker-entrypoint-initdb.d # DBの初期データ
- ./mysql/my.cnf:/etc/mysql/conf.d/my.cnf # bashで日本語文字化けする問題の解決
/initdb.d/create-table.sql
は以下のように記述する
CREATE TABLE contact_table (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) NOT NULL,
content_question TEXT NOT NULL,
postdate DATETIME(6) DEFAULT CURRENT_TIMESTAMP(6)
);
/mysql/my.cnf
は以下
[mysqld]
character_set_server = utf8mb4
collation-server=utf8mb4_unicode_ci
[mysql]
default-character-set = utf8mb4
[client]
default-character-set = utf8mb4
.gitignore
.DS_Store
.env.local
.env
.env
MYSQL_HOST=mysql
MYSQL_USER=root
MYSQL_PORT=3306
MYSQL_PASSWORD=password
MYSQL_DATABASE=contactform
TZ=Asia/Tokyo
必要なファイルの作成ができたのでこれからNext.jsの環境を作成します。
以下を実行
docker compose run --rm app npx create-next-app
- プロジェクト名は「.」を入力
- TypeScriptを選択
- ESLintを選択
- Tailwind CSSを選択
-
src/
ディレクトリを使うを選択 - App Routerを選択
これで/app
フォルダ内にNext.jsの環境ができました。
試しに起動するか確認しましょう
docker compose down # 念の為、コンテナを修了
docker compose up -d
localhost:3000
にアクセスするとNext.jsのページが表示されると思います。
データベースの確認は以下のコマンドでできます。
docker exec -it db sh # コンテナの中に入る
mysql -u root -p # DBにログイン
use contactform; # データベースを選択
show tables; # テーブル一覧を表示
exit # 表示できたらSQLを終了
exit # コンテナからも出る
これでcontact_table
のテーブルが表示されればうまくデータベースは動いています。
次に使用するライブラリのインストールをします。
docker exec -it ap sh # コンテナの中に入る
npm i framer-motion mysql2
npm i -D @chakra-ui/react @emotion/react @emotion/styled @types/mysql
exit # インストールできたらコンテナから出る
初期設定
/app/globals.css
を削除します。
/app/layout.tsx
を以下のように編集します。
- import './globals.css'
import { Inter } from 'next/font/google'
+ import { ChakraProvider } from "@chakra-ui/react"
const inter = Inter({ subsets: ['latin'] })
export const metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
- <body className={inter.className}>{children}</body>
+ <body className={inter.className}>
+ <ChakraProvider>{children}</ChakraProvider>
+ </body>
</html>
)
}
フォーム画面の実装
/app/page.tsx
を以下のようにします。
"use client"
import { Box, Button, Center, FormLabel, Heading, Input, Textarea } from "@chakra-ui/react"
import { ChangeEvent, FormEvent, useState } from "react"
export default function Home() {
const [userName, setUserName] = useState<string>("")
const userNameChange = (e: ChangeEvent<HTMLInputElement>) => {
setUserName(e.currentTarget.value)
}
const [userEmail, setUserEmail] = useState<string>("")
const userEmailChange = (e: ChangeEvent<HTMLInputElement>) => {
setUserEmail(e.currentTarget.value)
}
const [userContent, setUserContent] = useState<string>("")
const userContentChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
setUserContent(e.currentTarget.value)
}
const onSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault()
try {
// 送信処理
const response = await fetch("/api/send", {
method: "POST",
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: userName,
email: userEmail,
content: userContent
})
})
const json = await response.json()
console.log(json.message);
if (response.status === 200) {
setUserName("")
setUserEmail("")
setUserContent("")
}
} catch (error) {
console.log("送信失敗", error);
}
}
return (
<Center w="full" flexDir="column">
<Heading py={5}>お問合せフォーム</Heading>
<form onSubmit={onSubmit} style={{width: "100%"}}>
<Center gap={3} flexDir="column">
<Box w="40%" minW="250px">
<FormLabel htmlFor='name'>名前</FormLabel>
<Input id='name' placeholder='名前' value={userName} onChange={userNameChange}/>
</Box>
<Box w="40%" minW="250px">
<FormLabel htmlFor='email'>メールアドレス</FormLabel>
<Input id='email' placeholder='メールアドレス' value={userEmail} onChange={userEmailChange}/>
</Box>
<Box w="40%" minW="250px">
<FormLabel htmlFor='content'>内容</FormLabel>
<Textarea id='content' placeholder='内容' value={userContent} onChange={userContentChange}/>
</Box>
<Box w="40%" minW="250px" textAlign="center">
<Button type="submit">送信</Button>
</Box>
</Center>
</form>
</Center>
)
}
データベースとの接続
/app/lib/db
フォルダを作成し、/db
内にconnection.ts
を作成します。
// src/lib/connection.ts
import mysql from 'mysql2/promise';
const mysql_connection = async () =>
await mysql.createConnection({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_DATABASE,
});
export default mysql_connection;
送信のAPIの実装
/app/api/send
のパスでフォルダを作り、/send
内にroute.ts
を作成します。
import mysql_connection from "@/lib/db/connection";
import { NextRequest } from "next/server";
export async function POST(request: NextRequest) {
const body = await request.json();
if (!body.name || !body.email || !body.content) {
return new Response(JSON.stringify({ message: "入力値が不正です。" }), {
status: 500,
headers: { "Content-Type": "application/json" },
});
}
try {
const connection = await mysql_connection();
const query =
"INSERT INTO contact_table (name, email, content_question) VALUES (?, ?, ?)";
await connection.execute(query, [body.name, body.email, body.content]);
return new Response(JSON.stringify({ message: "送信に成功しました。" }), {
status: 200,
headers: { "Content-Type": "application/json" },
});
} catch (error) {
return new Response(JSON.stringify({ message: "送信に失敗しました。" }), {
status: 500,
headers: { "Content-Type": "application/json" },
});
}
}
これで送信機能の実装ができました。
試しにlocalhost:3000
にアクセスしてフォームを送信してみましょう。
送信できたら以下のコマンドでDBのコンテナに入ってテーブルにデータが追加されているか確認しましょう。
docker exec -it db sh # コンテナの中に入る
mysql -u root -p # DBにログイン
use contactform; # データベースを選択
select * from contact_table;
送信したデータが存在すればこれで完成です。
一覧表示画面
/app/contents/page.tsx
を以下のように作成
"use client"
import {useState, useEffect} from "react"
import {Center, Heading, Table, Thead, Tbody, Tr, Th ,Td} from "@chakra-ui/react"
export interface responceData {
id: number
name: string
email: string
content_question: string
postdate: string
}
const Page = () => {
const [contents, setContents] = useState<responceData[]>([])
const initFetch = async () => {
const response = await fetch("/api/contents")
const data = await response.json()
setContents(data.contents)
}
useEffect(() => {
initFetch()
},[])
return <Center w="full" flexDirection="column">
<Heading py={5}>お問合せ一覧</Heading>
<Table>
<Thead>
<Tr>
<Th>No.</Th>
<Th>名前</Th>
<Th>メール</Th>
<Th>内容</Th>
<Th>時間</Th>
</Tr>
</Thead>
<Tbody>
{contents.map((item, index) => (
<Tr key={index}>
<Td>{item.id}</Td>
<Td>{item.name}</Td>
<Td>{item.email}</Td>
<Td>{item.content_question}</Td>
<Td>{item.postdate}</Td>
</Tr>
))}
</Tbody>
</Table>
</Center>
}
export default Page
一覧取得API
/app/api/contents/route.ts
を以下のように作成
import mysql_connection from "@/lib/db/connection";
export async function GET() {
try {
const connection = await mysql_connection();
const result = await connection.query("SELECT * from contact_table");
connection.end();
return new Response(
JSON.stringify({ message: "取得に成功しました。", contents: result[0] }),
{
status: 200,
headers: { "Content-Type": "application/json" },
}
);
} catch (error) {
return new Response(JSON.stringify({ message: "取得に失敗しました。" }), {
status: 500,
headers: { "Content-Type": "application/json" },
});
}
}
これでlocalhost:3000/content
に行けば一覧表示画面、localhost:3000/api/contents
に行けば一覧結果が表示されます。
リポジトリのURL
cloneしてお試しください。
Discussion