Spring boot学習 (TypeScript  + RDS)

はじめに
---
spring boot 初学者向け
spring bootの学習のために、フロントからバックエンドまでを大まかにどうコードを書いていけばいいかを知りたくまとめました。
簡単なフロントとバックとデータベースを作成しましたので記事として残します
データベースはMySQLを利用しています
DynamoDBの場合は以下を参考

https://qiita.com/kazucb/items/7f908c89efe3de53e882

結果イメージ
フロントで「user」「email」を入力して、「DBに保存」ボタンを押すと、DBに保存される
フロントで検索したいuserを文字列として入力して、「検索」ボタンを押すと、DBに保存されているuserと対となるemailが表示される

作成手順
---
フロントエンドの準備
1.任意の場所に「spring-kotlin-vite-mysql-study」フォルダを作成
2.ターミナルで作成したフォルダへ移動しコマンドで「npm create vite」を入力し、Project nameはfrontendと入力しEnterを押し、次に矢印キーでReactを選択しEnterを押し、Typscritpを選択しEnterを押下する。
3.frontというフォルダが中身も含め作成されるので、これをVSCodeなどで開く
4.「npm install」を実行
5.以下のフォルダ構成で
App.tsx,
api.ts,
getEmailByUsername.ts
の３つのファイルを以下のコードで作成する
├── src
│   ├── App.css
│   ├── App.tsx
│   ├── api.ts
│   ├── assets
│   ├── getEmailByUsername.ts
│   ├── index.css
│   ├── main.tsx
│   └── vite-env.d.ts
├── tsconfig.json



App.tsxファイル
```
import { useState } from 'react'
import './App.css'
import {saveUser} from "./api.ts";
import {getEmailByUsername} from "./getEmailByUsername.ts";

function App() {
    const [username, setUserName] = useState("");
    const [email, setEmail] = useState("")
    const [searchUsername, setSearchUsername] = useState("");
    const [fetchedEmail, setFetchedEmail] = useState("")


    const handleSave = async () => {
        try{
            const response = await saveUser({username, email});
            alert(response.message)
        }
        catch(error){
            alert("Error saving user!")
        }
    }

    const handleSearch = async () => {
        console.log("searchUsername",searchUsername)
        try{
            const response = await getEmailByUsername(searchUsername);
            setFetchedEmail(response.email)
        } catch(error){
            alert("Error fetching email!")
        }
    }

  return (
    <>
        <div>
            <h3>DEMO</h3>
            <h4>spring boot + kotlin/ vite+Typscript/ MySQL</h4>
            <label>名前
                <input type="text" value={username} onChange={(e) => setUserName(e.target.value)}/>
            </label>
            <br />
            <label>メール
                <input type="text" value={email} onChange={(e) => setEmail(e.target.value)}/>
            </label>
            <br />
            <button onClick={handleSave}>DBに保存</button>
            <br />

            <label>検索する名前
                <input type="text" value={searchUsername} onChange={(e)=>setSearchUsername(e.target.value)}/>
            </label>
            <br />
            <button onClick={handleSearch}>検索</button>
            {fetchedEmail && (
                <div>
                    <h4>取得したメールアドレス:{fetchedEmail}</h4>
                </div>
            )}
        </div>
    </>
  )
}

export default App

```


api.ts
```
export const saveUser = async (user:{username:string, email:string}) => {
    const response = await fetch("http://localhost:8080/users",{
        method:"POST",
        headers:{"Content-Type": "application/json"},
        body:JSON.stringify(user)
    });

    if(!response.ok) throw new Error("Failed to save user");
    console.log("response",response)
    return response.json()
}
```

getEmailByUsername.ts
```
export const getEmailByUsername = async (username) => {
    console.log("searchUsername-getEmaulByUSername",username)
    const response = await fetch(`http://localhost:8080/users/${username}`,{
        method:"GET",
        headers:{"Content-Type": "application/json"},
    });
    if(!response.ok) throw new Error("Failed to get mail");
    return response.json()
}
```


6.「npm run dev」を実行
7.ブラウザを開き、http://localhost:5174/ を入力する
　(他にnpm run devしているアプリがある時は5174ではない場合があります)
8.以下のように表示されればfrontendの準備は完了
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2574492/4ab56419-aa1e-f6d9-bca5-ec928ebb2de5.png)


---
バックエンドの準備

1.spring initializrを開く
2.Artifactに「backend」と記入し、右上のADD DEPENDENCIESを押してSring webを選択する
3.下部のGENERATEを押す
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2574492/5e20c153-3ad7-3cd3-be6b-c18b40c5bce6.png)
4.ダウンロードしたzipファイルを解凍して、「spring-kotlin-vite-mysql-study」の中に保存する
5.backendフォルダを開く
6.以下のフォルダ構成で
Userクラス
UserControllerクラス
UserRepositoryクラス
UserServiceクラス
application.properties
build.gradle.kts
docker-compose.yml
の7つのファイルを以下のコードで作成する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2574492/2b2202c9-21a8-bb1e-0402-f44555a5122c.png)


Userクラス
```package com.example.backend

import jakarta.persistence.Entity
import jakarta.persistence.GeneratedValue
import jakarta.persistence.GenerationType
import jakarta.persistence.Id

@Entity
data class User(
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id:Long = 0,
    val username:String,
    val email:String
)
```

UserControllerクラス
```
package com.example.backend

import org.springframework.web.bind.annotation.*

@RestController
@RequestMapping("/users")
@CrossOrigin(origins = ["http://localhost:5173"])//CORS対応

class UserController (private val service:UserService){
    @PostMapping
    fun createUser(@RequestBody user:User):Map<String, String>{
        service.saveUser(user)
        return mapOf("message" to "User saved successfully!")
    }

    @GetMapping("/{username}")
    fun getEmailByUsername(@PathVariable username: String): Map<String, String> {
        val email: String? = service.findEmailByUsername(username)
        return mapOf("email" to (email ?: "User not found"))
    }

}
```

UserRepositoryクラス
```
package com.example.backend

import org.springframework.data.jpa.repository.JpaRepository

interface UserRepository : JpaRepository<User, Long> {
    fun findByUsername(username: String): User?
}
```
UserServiceクラス
```package com.example.backend

import org.springframework.stereotype.Service

@Service
class UserService (private val repository:UserRepository){
    fun saveUser(user:User):User = repository.save(user)

    fun findEmailByUsername(username:String):String?{
        val user = repository.findByUsername(username)
        return user?.email
    }
}
```

application.properties
```
spring.application.name=mysql-test
spring.datasource.url=jdbc:mysql://localhost:3306/demo
spring.datasource.username=user
spring.datasource.password=password6
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

spring.cloud.config.enabled=false
#spring.config.import=configserver:

```


build.gradle.kts
```
plugins {
	kotlin("jvm") version "1.9.25"
	kotlin("plugin.spring") version "1.9.25"
	id("org.springframework.boot") version "3.4.0"
	id("io.spring.dependency-management") version "1.1.6"

	kotlin("plugin.jpa") version "1.9.10"
}

group = "com.example"
version = "0.0.1-SNAPSHOT"

java {
	toolchain {
		languageVersion = JavaLanguageVersion.of(17)
	}
}

repositories {
	mavenCentral()
}

extra["springCloudVersion"] = "2024.0.0"

dependencies {
	implementation("org.springframework.boot:spring-boot-starter-web")
	implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
	implementation("org.jetbrains.kotlin:kotlin-reflect")
	implementation("org.springframework.cloud:spring-cloud-starter-config")
	runtimeOnly("com.mysql:mysql-connector-j")
	testImplementation("org.springframework.boot:spring-boot-starter-test")
	testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
	testRuntimeOnly("org.junit.platform:junit-platform-launcher")

	testImplementation("org.mockito.kotlin:mockito-kotlin:5.1.0") // 最新版を使用
	testImplementation("org.springframework.boot:spring-boot-starter-test") {
		exclude(group = "org.mockito", module = "mockito-core")
	}

	implementation("jakarta.persistence:jakarta.persistence-api:3.1.0") // Jakarta Persistence API
	implementation("org.springframework.boot:spring-boot-starter-data-jpa") // Spring Data JPA
}


dependencyManagement {
	imports {
		mavenBom("org.springframework.cloud:spring-cloud-dependencies:${property("springCloudVersion")}")
	}
}

kotlin {
	compilerOptions {
		freeCompilerArgs.addAll("-Xjsr305=strict")
	}
}

tasks.withType<Test> {
	useJUnitPlatform()
}

```

docker-compose.yml
```
version: '3.8'

services:
  mysql:
    image: mysql:latest
    container_name: mysql-kotlin-2024_12_09
    restart: always
    environment:
      MYSQL_USER: user
      MYSQL_ROOT_PASSWORD: mysql112233
      MYSQL_PASSWORD: password6
      MYSQL_DATABASE: demo
    ports:
      - "3306:3306"
    volumes:
      - ./mysql_data:/var/lib/mysql  # ローカルディレクトリを指定

volumes:
  mysql_data:
```

バックエンドの起動方法
9.dockerの起動
    Macターミナルで、上記で作成したdocker-compose.ymlファイルがある場所で
    ```
    docker compose up -d  
    ```
を実行
10.BackendApplicaiton.ktでバックエンド起動（再生ボタン押下）

フロント→バックエンド→データベースへの動作確認
11.先ほどnpm run devで起動したフロント画面で、userとemailを入力して、「DBに保存」ボタンを押す。
12.先ほどuser欄で入力した文字を検索する名前の欄に入力して「検索」を押すと、emailが表示される
13.以上

これでフロントエンド→バックエンド→データベースの動作がspringではどうやって動作するのがかイメージするのに役立てればと思います。


参考
---
mysqlへ直接データが保存されているかの確認方法
・ターミナルで
```
docker exec -it mysql-kotlin-2024_12_09 mysql -u root  -p
```
```
mysql112233 
```
```
USE demo; 
```
```
select * from user;  
```
以下のようにデータがあるかを確認できる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2574492/cf169b05-6f90-8177-f24a-7748d3c4fb5f.png)

8080が起動しているか確認するコマンド
lsof -i:8080
停止は kill プロセスID

以上、もし何か指摘があればコメントいただければ幸いです