Open1

バックエンドのお勉強

ふみふみ

初投稿

ということで第1回目はバックエンドのお勉強ということでやっていきましょう
作っていくのは簡単なアカウント登録とログインのやつです(ChatGPT先生ありがとう...)

今回使っていくのは

  • bcrypt
  • cors
  • dotenv
  • express
  • jsonwebtoken
  • pg
    です

自身の勉強のためにZennを始めたので,用語の勉強ということでそれぞれの役割を下にメモしていきます.(もし間違ってるところがあったら教えてくださると幸いです...)

bcrypt

パスワードをハッシュ化するために使う

cors

CORSとはオリジン間リソース共有のこと.(なんじゃそりゃ?)
同一オリジンポリシーの制限を回避するために利用する機能のことらしい.
↑とあるオリジンから取得したリソースから,別のオリジンのリソースへのアクセスを禁止するブラウザの機能のこととのこと.(オリジンってなんすか?)

オリジン

MDNによると

ウェブコンテンツのオリジン (Origin) は、ウェブコンテンツにアクセスするために使われる URL の スキーム (プロトコル)、 ホスト (ドメイン)、 ポート番号 によって定義されます。スキーム、ホスト、ポート番号がすべて一致した場合のみ、 2 つのオブジェクトは同じオリジンであると言えます。

とのこと.要するにいわゆるリンクの一部のことを表してる?
https:(スキーム)//hogehoge.com(ホスト):443(ポート番号)
に分けられるらしい.

CORSの話に戻ると,どうやらセキュリティのためにほかのオリジンからリソースを取得させないようにするところをこれを使うことによって取得できるようにするものらしい(ホワイトリスト的なイメージ?).

dotenv

大事な情報(APIキーとか)を設定した.envファイルを使って環境変数を設定するライブラリ

express

バックエンドを作るためのフレームワーク
APIとかルーティングとかで使う

jsonwebtoken

調べてもいまいち理解できなかったのでChatGPTに聞いてみた

JWT(JSON Web Token) は、ユーザーのログイン状態を維持したり、ユーザーの情報を安全にやりとりするためのトークン形式のこと!

らしい.あとで出てくるのでその時に触れるとします.

pg

PostgreSQLのこと.オープンソースのリレーショナルデータベース管理システム(RDBMS)らしい...
正直SQLiteのを使った方がよかったっていうのは内緒

今回のファイルツリー

LoginPrac/
├── db.js
├── index.js
├── package.json
├── .env
├── routes/
│ └── auth.js
└── front/
└── ...(フロントエンドのファイル群)

db.js
const { Pool } = require("pg");

//環境変数から接続URLを取得
const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
});

async function connectDB() {
  try {
    await pool.connect();
    console.log("Connected to the database");
  } catch (error) {
    console.error("Error connecting to the database", error);
    process.exit(1); 
  }
}

// ほかのファイルからクエリできるようにpoolをエクスポート
module.exports = {
  connectDB,
  query: (text, params) => pool.query(text, params),
};

処理としては

  • Poolを使ってコネクションプールを作成,.envに書いておいたURLを取得
  • connectDB関数でデータベースへの接続を試みる
  • module.exportsでほかのファイルからクエリ(データベースに対する命令)できるようにエクスポート
index.js
const express = require("express");
const app = express();
const authRoutes = require("./routes/auth"); //ここで処理
const { connectDB } = require("./db");
const cors = require("cors");
const path = require("path");

// 環境変数の読み込み
require("dotenv").config();

// ミドルウェア
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(express.static(path.join(__dirname, "front"))); //テストコード用

// ルーティング
app.use("/api/auth", authRoutes);

// サーバーを実行
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`);
});

処理としては

  • 環境変数の読み込み
  • ミドルウェア(reqとresの間に挟まる処理)
    • app.use(cors()でオリジン間のやり取りを許可
    • express.json()でJSONでを使えるように
    • express.urlencoded()URLエンコード(URLの後ろにつく長いやつ)されたリクエストボディ(中身のデータ)を扱えるように
  • /api/authにきたリクエストをauthRoutesで処理
  • 今回はポート3000番でサーバーを起動
    という流れ
auth.js
const express = require("express");
const router = express.Router();
const bcrypt = require("bcrypt");
const jwt = require("jsonwebtoken");
const db = require("../db");

// 新しいユーザー登録のためのエンドポイント
router.post("/register", async (req, res) => {
  const { email, password } = req.body;

  // 入力チェック
  if (!email || !password) {
    return res.status(400).json({ message: "Email and password are required" });
  }

  try {
    // すでに使われてるかどうか
    const existingUser = await db.query(
      "SELECT * FROM users WHERE email = $1",
      [email]
    );
    if (existingUser.rows.length > 0) {
      return res.status(400).json({ message: "User already exists" });
    }

    // パスワードをハッシュ化
    const hashedPassword = await bcrypt.hash(password, 10);

    // ユーザーを登録
    await db.query("INSERT INTO users (email, password) VALUES ($1, $2)", [
      email,
      hashedPassword,
    ]);

    res.status(201).json({ message: "User registered successfully" });
  } catch (error) {
    console.error("Error registering user:", error);
    res.status(500).json({ message: "Internal server error" });
  }
});

// ログインのためのエンドポイント
router.post("/login", async (req, res) => {
  const { email, password } = req.body;

  // 入力チェック
  if (!email || !password) {
    return res.status(400).json({ message: "Email and password are required" });
  }

  try {
    // データベースからユーザーを取得
    const result = await db.query("SELECT * FROM users WHERE email = $1", [
      email,
    ]);
    const user = result.rows[0];

    if (!user) {
      return res.status(400).json({ message: "Invalid email or password" });
    }

    // パスワードの照合
    const isMatch = await bcrypt.compare(password, user.password);
    if (!isMatch) {
      return res.status(400).json({ message: "Invalid email or password" });
    }

    // JWTトークンの発行
    const token = jwt.sign({ id: user.id }, process.env.JWT_SECRET, {
      expiresIn: "1h",
    });

    res.json({ token });
  } catch (error) {
    console.error("Error logging in user:", error);
    res.status(500).json({ message: "Internal server error" });
  }
});

module.exports = router;

処理としては

  • /registerでユーザー登録
  • リクエストボディからemailとpasswordを取得
  • 入力チェック(ダメだったら400エラー)
  • ユーザーがすでに存在してるかチェック($1で[email]の値が入る)
  • existingUserのrowに1件以上データがあれば登録処理を中断
  • パスワードをハッシュ化
  • emailとハッシュ化したパスワードをデータベースに登録
  • 成功したらメッセージを送信
    -/loginでログイン
  • リクエストボディからemailとpasswordを取得
    -入力チェック
  • resultにクエリの結果入れる
  • userにresultの最初の要素を入れる
  • userが存在しなかったらemailかパスワードが存在しないと送信
  • 入力されたパスワードとデータベースに保存されているハッシュ化されたパスワードを比較
  • 合ってたらJWTトークンを生成,レスポンスとして返す
    という流れ
    ここでJWTトークンが出てきましたが,テストコードで出力された値が
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwiaWF0IjoxNzQ4MDIxNjcyLCJleHAiOjE3NDgwMjUyNzJ9.HbwhschJh6ocs89w7SC6Hi7fSJs9ZCkJyP58D3NZZCU

となっている.ChatGPTに聞くとJWTトークンは3つの個所があり,アルゴリズムとかの情報,ユーザーIDとかの中身,改ざんチェック用の署名に分かれている.

あとはテストコードを作って(Agentモードすげぇ)実際に動作確認しました.(登録できたよ,やったね!)

ということで今回はバックエンドのお勉強ということでデータベースと認証の練習をしました.
いやー知らないことだらけで調べながらこのメモを書きました...
それぞれの役割がわかったので次からは自力で少しは書けるようになるかな?と思いたい...