概要
この記事では、初心者の方がこの記事を読みながら周辺知識を調べてWebアプリを作成して公開までできるように、実際の環境構築から公開までした内容をまとめました。今回は環境構築しローカルで作動させるまでです。
使用するもの
- Node.js
JavaScriptでサーバーサイドプログラムを実行するためのランタイム環境。Next.jsを動作させる基盤として使用します。 - Next.js
Reactベースのフレームワークで、フロントエンドとバックエンド(APIエンドポイント)を統合的に開発できます。 - NextAuth
認証と認可を簡単に実装できるライブラリ。今回はSpotifyアカウントを利用したOAuth認証を設定します。 - Prisma
モダンなORM(Object-Relational Mapping)ツール。PostgreSQLとのデータベース操作ができます。 - Recoil
Reactで状態管理を行うためのライブラリ。アプリ内のグローバルな状態管理を行います。 - Docker
Dockerのコンテナを使用してPostgreSQLのデータベースを構築します。また、アプリ公開の際にDockerでコンテナ化して公開します。サーバーの環境設定不要で公開することができます。 - PostgreSQL
リレーショナルデータベース。Dockerで構築します。 - Spotify API
Spotifyの音楽データにアクセスするためのAPI。 - Bootstrap
アプリのレスポンシブルデザインにBootstrapのテンプレートを使用します。
公開アプリ
GitHub
開発環境の構築
Node.jsのインストール
Node.jsはJavaScriptの実行環境で、バックエンドや開発サーバーを動かすのに必要で、Next.jsでアプリ作成するために必須です。次のリンクから最新バージョンをダウンロードしてインストールします。
nvmのインストール
nvmは複数のNode.jsのバージョンを管理できるツールです。各種パッケージの依存関係のエラーを回避するため特定のバージョンで環境を設定します。
nvmを使って、Node.jsのバージョン18をインストールします。
nvm install 18
Node.jsのバージョン18を使用するように切り替えます。
nvm use 18
Next.jsのプロジェクト作成
VSCodeのターミナルを使用して、Next.jsのプロジェクトを作成するフォルダへ移動し次のコマンドでプロジェクトを作成します。
npx create-next-app@latest .
ターミナルでスクリプトが実行できない場合は以下を実行します。PowerShellのスクリプト実行ポリシーを変更してスクリプトを実行可能とします。
Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
プロジェクトのフォルダ構成は以下のようになります。
project/
├── .next/
├── node_modules/
├── public/
├── src/
│ ├── app/
│ │ ├── api/ # バックエンドのAPIを作成
│ │ ├── components/ # 再利用するコンポーネントを作成
│ │ ├── favorites/ # お気に入りのページを作成
│ │ ├── fonts/
│ │ ├── playlists/ # プレイリストのページを作成
│ │ ├── top-tracks/ # トップトラックのページを作成
│ │ ├── global.css
│ │ ├── layout.tsx # アプリ全体のレイアウトを設定
│ │ ├── next.auth.d.ts # NextAuth.jsで使用する型定義
│ │ └── page.tsx # トップページ
│ ├── lib/ # ユーティリティ関数を作成
│ └── types/ # TypeScriptの型定義
├── .env # 環境変数ファイルを設定
├── .eslintrc.json
├── next-env.d.ts
├── next.config.js # Next.js設定
├── package-lock.json
├── package.json
├── postcss.config.mjs
├── README.md
├── tailwind.config.ts
└── tsconfig.json
Dockerのインストール
アプリで使用するデータベースをPostgreSQLとし、Dockerコンテナを使用して起動することにしますので、Docker Desktopをインストールします。
PostgreSQLの設定
Dockerを使ってPostgreSQLの環境を構築します。初めにDockerのコンテナを設定・起動するためのファイルを作成します。
version: "3.9"
services:
postgres:
image: postgres:15
container_name: postgres_container
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
アプリでPostgreSQLを使用するための環境変数を設定します。
DATABASE_URL="postgresql://my_user:my_password@localhost:5432/my_database?schema=public"
POSTGRES_USER=my_user
POSTGRES_PASSWORD=my_password
POSTGRES_DB=my_database
docker-compose.yml
の設定でpostgreSQLコンテナを起動します。
docker-compose up –d
コンテナ起動する前にDocker Desktopを起動してください。
コンテナを停止するときは以下のコマンドを入力します。
docker-compose down
アプリ起動
この段階で一度アプリを起動してみます。必要なパッケージをインストールします。
npm install
ローカル環境でアプリを起動します。
npm run dev
アプリ起動後に以下で確認します。
SpotifyAPIの連携
Spotify APIを使うことで、Spotifyアカウントのデータにアクセスし、音楽データを取得できます。Spotify Developer Dashboardでのアプリ登録、APIキーを取得します。
Spotify Web API を操作するための Node.js ライブラリ spotify-web-api-node をインストールします。このライブラリを使うと、SpotifyのAPIに簡単にアクセスできます。
npm install spotify-web-api-node
npm install --save-dev @types/spotify-web-api-node
npm install node-fetch
npm install ts-node --save-dev
SpotifyAPIを使用するため環境変数を設定します。
DATABASE_URL="postgresql://my_user:my_password@localhost:5432/my_database?schema=public"
POSTGRES_USER=my_user
POSTGRES_PASSWORD=my_password
POSTGRES_DB=my_database
+ SPOTIFY_CLIENT_ID= # ここにSpotifyのクライアントIDを入力
+ SPOTIFY_CLIENT_SECRET= # ここにシークレットキーを自由に設定
Next.jsのアプリ構築
NextAuth
NextAuthは、簡単に認証機能を実装できるライブラリです。NextAuthを使用するためアプリの環境変数を設定します。
DATABASE_URL="postgresql://my_user:my_password@localhost:5432/my_database?schema=public"
POSTGRES_USER=my_user
POSTGRES_PASSWORD=my_password
POSTGRES_DB=my_database
SPOTIFY_CLIENT_ID=
SPOTIFY_CLIENT_SECRET=
+ NEXTAUTH_SECRET= # ここにシークレットキーを自由に設定
+ NEXTAUTH_URL=http://localhost:3000
NextAuthを使用するためのAPIエンドポイントを設定します。
import NextAuth, { NextAuthOptions } from "next-auth";
import { authOptions } from "@/lib/auth";
// NextAuthのインスタンスをエクスポート
const handler = NextAuth(authOptions as NextAuthOptions);
export { handler as GET, handler as POST };
Spotify認証の設定をします。
import { Session } from "next-auth";
import { JWT } from "next-auth/jwt";
import SpotifyProvider from "next-auth/providers/spotify";
import { prisma } from "@/lib/prisma";
// Spotify Web APIから取得する情報
const scopes = [
"user-read-private",
"user-read-email",
"user-top-read",
"playlist-read-private",
"playlist-read-collaborative",
"playlist-modify-private",
"playlist-modify-public",
"user-read-playback-state",
"user-read-currently-playing",
"user-modify-playback-state",
"user-library-read",
"user-library-modify",
].join(" ");
// NextAuthの設定
export const authOptions = {
providers: [
SpotifyProvider({
clientId: process.env.SPOTIFY_CLIENT_ID!,
clientSecret: process.env.SPOTIFY_CLIENT_SECRET!,
authorization: `https://accounts.spotify.com/authorize?scope=${scopes}`,
}),
],
callbacks: {
// NOTE: userはspotify側で自動で設定される(user.idはSpotifyのIDが設定される)
async jwt({ token, account, user }: {
token: JWT;
account?: Record<string, any>;
user?: Record<string, any>;
}) {
if (account && account.provider === "spotify") {
console.log('token:', token);
console.log('account:', account);
console.log('user:', user);
token.spotifyId = account.providerAccountId;
token.accessToken = account.access_token;
token.refreshToken = account.refresh_token;
token.accessTokenExpires = Date.now() + account.expires_at * 1000;
// 新規ユーザの場合、データベースにユーザを作成
if (user) {
await prisma.user.upsert({
where: { spotifyId: user.id },
update: {},
create: {
name: user.name || "",
email: user.email || "",
spotifyId: user.id,
refreshToken: token.refreshToken as string,
},
});
}
}
return token;
},
async session({ session, token }: {
session: Session;
token: JWT & { spotifyId?: string; accessToken?: string; refreshToken?: string };
}) {
if (token.spotifyId) {
// NOTE: userにはNexAuthがデフォルトで提供するプロパティ(name, email, image等)が含まれる
session.user.spotifyId = token.spotifyId;
}
return session;
},
},
pages: {
signIn: '/auth/signin',
},
};
標準のセッションにSpotify IDを追加するため型定義をします。
import NextAuth from "next-auth";
declare module "next-auth" {
interface Session {
user: {
name?: string | null;
email?: string | null;
image?: string | null;
spotifyId?: string; // 追加したプロパティ
};
}
}
また、アプリ全体で使用するその他の型定義をします。
export type Track = {
id: string;
name: string;
artist: string;
album: string;
popularity: number;
imageUrl: string;
trackUrl: string;
};
export type Playlist = {
id: string;
name: string;
description: string | null;
imageUrl: string | null;
tracks: Track[];
};
export type Device = {
id: string;
name: string;
};
export type CacheExpiry = {
id: number;
spotifyId: string;
type: string;
expiresAt: string; // Prismaから返ってくる日時はISO文字列のため
createdAt: string; // Prismaから返ってくる日時はISO文字列のため
}
export type User = {
id: string; // SpotifyのユーザーID
display_name: string; // ユーザー名
email: string; // ユーザーのメールアドレス
images?: { url: string }[]; // プロフィール画像のURL(オプション)
};
export type SpotifyData = {
user?: User;
topTracksIn4Weeks?: Track[];
topTracksIn6Months?: Track[];
topTracksInAllTime?: Track[];
playlists?: (Playlist & { tracks: Track[] })[];
devices?: Device[];
savedTracks?: Track[];
}
export type FavoritePeriod = {
id: number;
favoriteId: number;
startDate: string; // Prisma の DateTime 型は ISO 文字列として返される
endDate?: string; // nullable な場合はオプショナル
};
export type Favorite = {
id: number;
userSpotifyId: string;
spotifyTrackId: string;
spotifyTrackName: string;
periods: FavoritePeriod[]; // 関連する期間のリスト
};
Prisma
Prismaは、データベース操作を簡素化するORMツールです。Prismaを使ってPostgreSQLと接続します。Prisma CLIツールをインストールします。
npm install prisma --save-dev
npm install @prisma/client
Prismaの設定ファイルを初期化するコマンドです。このコマンドを実行すると、Prismaの設定ファイル(prisma/schema.prisma)やデータベース接続設定を含むprismaディレクトリがプロジェクトに作成されます。これにより、Prismaを使用したデータベースモデルの作成やマイグレーションが行えるようになります。
npx prisma init
データベースで使用するデータの設定を行います。
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
name String?
email String? @unique
spotifyId String @unique
refreshToken String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
schedules Schedule[]
}
model Favorite {
id Int @id @default(autoincrement())
userSpotifyId String
spotifyTrackId String
spotifyTrackName String
periods FavoritePeriod[]
@@unique([userSpotifyId, spotifyTrackId]) // 複合一意制約
}
model FavoritePeriod {
id Int @id @default(autoincrement())
favoriteId Int
startDate DateTime
endDate DateTime?
favorite Favorite @relation(fields: [favoriteId], references: [id])
}
model SpotifyCache {
id Int @id @default(autoincrement())
spotifyId String
type String
expiresAt DateTime
createdAt DateTime @default(now())
@@unique([spotifyId, type])
}
schema.prisma
の設定にもとづきデータベースを作成します。
npx prisma migrate dev --name init
アプリで使用できるように設定します。
px prisma generate
データベースをブラウザで管理することができます。以下のコマンドを入力して、
npx prisma studio
以下にアクセスします。
Recoil
Recoilは、状態管理ライブラリで、Reactアプリケーションのデータの流れを管理します。Recoilをインストールします。
npm install recoil
コンポーネント間で共有する状態を設定します。
import { atom } from "recoil";
import { Track, Playlist, Favorite } from "@/types/spotify"
export const savedTracksState = atom<Track[]>({
key: "savedTracksState",
default: [],
});
export const favoritesState = atom<Favorite[]>({
key: "favoritesState",
default: [],
});
export const tracksIn4WeeksState = atom<Track[]>({
key: "tracksIn4WeeksState",
default: [],
});
export const tracksIn6MonthsState = atom<Track[]>({
key: "tracksIn6MonthsState",
default: [],
});
export const tracksInAllTimeState = atom<Track[]>({
key: "tracksInAllTimeState",
default: [],
});
export const playlistsState = atom<Playlist[]>({
key: "playlistsState",
default: [],
});
export const selectedViewState = atom<"Chart" | "Top Tracks in 4 Weeks" | "Top Tracks in 6 Months" | "Top Tracks of All Time" | "Favorites">({
key: "selectedViewState",
default: "Chart",
});
export const selectedPlaylistState = atom<Playlist | null>({
key: "selectedPlaylistState",
default: null,
});
export const createPlaylistModeState = atom<boolean>({
key: "createPlaylistModeState",
default: false,
});
Echarts
Echartsは、データを可視化するためのグラフライブラリです。Echartsをインストールします。
npm install echarts echarts
Bootstrap Templatesの使用
Bootstrapテンプレートを使用して、アプリのデザインを迅速に整えることができます。
ダウンロードしたファイルをpublic\assets
に保存します(必要なものを抜粋、一部修正しています)。
アプリ完成
完成したアプリのフォルダ構成となります。
project/
├── .next/
├── node_modules/
├── prisma/
│ ├── migrations/
│ └── schema.prisma # データベース関係設定
├── public/
│ └── assets/ # Bootstrapのテンプレートファイル
├── src/
│ ├── app/
│ │ ├── api/ # バックエンドのAPIを作成
│ │ ├── components/ # 再利用するコンポーネントを作成
│ │ ├── favorites/ # お気に入りのページを作成
│ │ ├── fonts/
│ │ ├── playlists/ # プレイリストのページを作成
│ │ ├── top-tracks/ # トップトラックのページを作成
│ │ ├── global.css
│ │ ├── layout.tsx # アプリ全体のレイアウトを設定
│ │ ├── next.auth.d.ts # NextAuth.jsで使用する型定義
│ │ └── page.tsx # トップページ
│ ├── lib/ # ユーティリティ関数を作成
│ └── types/ # TypeScriptの型定義
├── .env # 環境変数ファイルを設定
├── .eslintrc.json
├── next-env.d.ts
├── next.config.js # Next.js設定
├── package-lock.json
├── package.json
├── postcss.config.mjs
├── README.md
├── tailwind.config.ts
└── tsconfig.json
公開アプリ
GitHub
次回、公開サーバーへ公開するまでを説明します。