Spring BootでバックエンドAPIを実装する
前回からしばらく間が空いてしまいましたが、第3回となる今回は、Spring BootでバックエンドAPIを作っていこうと思います。
ちなみに前回の記事はコチラ。
今回はSpring BootのJavaアプリケーションをVSCodeで開発していきますので、VSCodeに以下の拡張機能を入れておいてくださいね。
- Extension Pack for Java
- Spring Boot Extension Pack
- Postman(REST APIのテストで使用)
プロジェクトの作成
それではまずプロジェクトを作成していきます。
VSCodeで前回までのワークスペースを開いたら、Crtl+Shift+p
でコマンドパレットを起動します。
ここにspring
と入力して、Spring Initializr: Create a Maven Project…を選択します。
選択するといくつか情報を聞かれるので入力します。今回入力した値は以下の通りです。
入力項目 | 入力値 |
Specify Spring Boot version. | 3.3.5 |
Specify project language. | Java |
Input Groud Id for your project. | jp.co.tdi |
Input Artifact Id for your project. | miso-backend |
Specify packaging type. | Jar |
Specify Java version. | 17 |
Choose dependencies | Spring Boot DevTools、Spring Web、lombok |
これでプロジェクトが作成されたと思います。
Springの設定ファイルは、プロパティファイルよりもYAMLのほうが好みなので、src\main\resourcesの下にあるapplication.propertiesを右クリックして、Convert .properties to .yamlしておきます。(application.propertiesは削除しておきます)
バックエンドAPIの実装
パスワードを生成するバックエンドAPIを実装していきます。
簡単なロジックなので、コントローラだけで済ませてしまいます。
まず、VSCodeのsrc\main\java\jp\co\tdi\miso-backendを右クリックして、New Java Package…を選択してcontrollerパッケージを作成してください。
controllerパッケージが作成できたら、さらにそれを右クリックして、New Java File→Class…を選択してPasswordGeneratorクラスを作成してください。
PasswordGeneratorクラスを次のように実装します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
package jp.co.tdi.miso_backend.controller; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.security.SecureRandom; @RestController @RequestMapping("/password") public class PasswordGenerator { /** * ランダムなパスワードを生成して返します。 * * @return 生成されたパスワード */ @PostMapping public String generatePassword() { int length = 12; String characterSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+"; SecureRandom random = new SecureRandom(); StringBuilder password = new StringBuilder(length); for (int i = 0; i < length; i++) { int index = random.nextInt(characterSet.length()); password.append(characterSet.charAt(index)); } return password.toString(); } } |
/passwordというURIにリクエストがあったときにランダムなパスワードを生成して返すREST APIができました。
実行して確認してみましょう。
メニューから実行→デバッグなしで実行を選択し、Postmanの拡張機能からリクエストを投げてみます。
Postmanから正常にランダムなパスワードが生成されたことを確認できました。
コンテキストルートの変更およびCORS設定の追加
/apiへのリクエスト時にバックエンドアプリケーションを実行したいので、コンテキストルートを/apiに変更します。
また、REST APIなのでCORS対応します。
一旦はローカルで動いているフロントエンドからのリクエストなので、http://localhost:3000から受け付けられるようにします。
ただし、この値は環境によって変わるのが目に見えているので、環境変数から与えられるようにしておきます。
はじめにapplication.ymlを以下のように変更します。
1 2 3 4 5 6 7 8 9 10 |
spring: application: name: miso-backend server: servlet: context-path: /api cors: allowedOrigins: ${ALLOWED_ORIGINS} |
この変更により、先ほどhttp://localhost:8080/passwordだったURLが、http://localhost:8080/api/passwordになりました。
次はこの値を読み取って、CORS設定するようにします。
VSCodeのsrc\main\java\jp\co\tdi\miso-backendを右クリックして、New Java Package…を選択してconfigパッケージを作成してください。
configパッケージが作成できたら、さらにそれを右クリックして、New Java File→Class…を選択してCorsPropertiesクラスを作成してください。
CorsPropertiesクラスを次のように実装します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
package jp.co.tdi.miso_backend.config; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import lombok.Data; /** * CORS設定のプロパティを管理するクラスです。 * プロパティはapplication.ymlまたはapplication.propertiesファイルから読み込まれます。 */ @Component @ConfigurationProperties(prefix = "cors") @Data public class CorsProperties { /** * 許可されるオリジンのリストをカンマ区切りで指定します。 */ private String allowedOrigins; } |
このクラスがapplication.ymlから値を取得してくれます。
configパッケージを右クリックして、New Java File→Class…を選択してWebConfigクラスを作成してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
package jp.co.tdi.miso_backend.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * WebアプリケーションのCORS設定を構成するクラスです。 */ @Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private CorsProperties corsProperties; /** * CORSのマッピングを追加します。 * * @param registry CorsRegistryオブジェクト */ @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**").allowedOrigins(corsProperties.getAllowedOrigins()) .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS").allowedHeaders("*").allowCredentials(true); } } |
ローカルでの実行時に環境変数を設定するために起動構成を作成します。
メニューから、実行→構成の追加…を選択してください。
ワークスぺスルートの.vscodeの下にlaunch.jsonというファイルが作成されるので、これを編集します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
{ "version": "0.2.0", "configurations": [ { "type": "java", "name": "MisoBackendApplication", "request": "launch", "mainClass": "jp.co.tdi.miso_backend.MisoBackendApplication", "projectName": "miso-backend", "env": { "ALLOWED_ORIGINS": "http://localhost:3000" } } ] } |
一度Spring Bootのプロセスを停止してから再び実行すると、この起動設定で実行されます。
フロントエンドからの呼び出し
作成したREST APIを前回作成したフロントエンドから呼び出すようにします。
呼び出すために以下のコマンドを実行して、axiosのパッケージをインストールします。
1 |
npm install axios |
インストールできたら、App.tsxを変更してAPIを呼び出すようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
import { useCallback, useEffect, useState } from 'react'; import { useRecoilState } from 'recoil'; import { fetchAuthSession, signInWithRedirect, signOut } from 'aws-amplify/auth'; import { Hub } from 'aws-amplify/utils'; import userState from './stores/userState'; import './App.css'; import axios from 'axios'; function App() { const [user, setUser] = useRecoilState(userState); const [password, setPassword] = useState(""); const [showMessage, setShowMessage] = useState(false); const getUser = useCallback(async () => { try { const session = await fetchAuthSession(); const idToken = session.tokens?.idToken; if (idToken) { setUser({ name: idToken.payload.name as string, email: idToken.payload.email as string, picture: idToken.payload.picture as string, }); } } catch (err) { console.log(err); setUser({ name: "", email: "", picture: "" }); } }, [setUser]); const signIn = async () => { getUser(); if (user.name === "") { signInWithRedirect({ provider: "Google" }); } }; const generatePassword = async () => { try { const apiUrl = import.meta.env.VITE_API_URL_BASE || `${window.location.origin}/api`; const response = await axios.post(`${apiUrl}/password`); const response = await axios.post(`${import.meta.env.VITE_API_URL_BASE}/password`); setPassword(response.data); } catch (err) { console.log(err); } }; const handleClick = () => { if (password) { navigator.clipboard.writeText(password); setShowMessage(true); setTimeout(() => setShowMessage(false), 2000); } }; useEffect(() => { const unsubscribe = Hub.listen("auth", (data) => { const { payload } = data; switch (payload.event) { case "signInWithRedirect": getUser(); break; case "signInWithRedirect_failure": console.log("Sign in with redirect failed"); break; case "signedOut": setUser({ name: "", email: "", picture: "" }); break; default: console.log(payload); } }); getUser(); return unsubscribe; }, [getUser, setUser]); return ( <> {user.name !== "" ? ( <> <h3>ようこそ<img draggable="false" data-mce-resize="false" data-mce-placeholder="1" data-wp-emoji="1" class="emoji" alt="😄" src="https://s.w.org/images/core/emoji/13.1.0/svg/1f604.svg"> {user.name}さん</h3> <img src={user.picture} alt={user.name} /> <h1>Password Generator</h1> <div className="card"> <button onClick={generatePassword}> Generate </button> <button onClick={async () => { signOut(); }} > Sign Out </button> <p onClick={handleClick} style={{ cursor: 'pointer' }}> {password ? password : "生成されたパスワードがここに表示されます"} </p> {showMessage && <div className="snackbar">パスワードがコピーされました</div>} </div> </> ) : ( <> <button onClick={async () => { signIn(); }} > Sign in </button> </> )} </> ); } export default App; |
少々長いので、変更点を解説します。
- 生成されたパスワードとスナックバーの表示状態を管理するためにステートを定義しました。(11、12行目)
- パスワード生成のREST APIを呼び出す関数generatePasswordを追加しました。(38~47行目)
- 環境変数VITE_API_URL_BASEがあるときはそれを、ないときはリクエストされたオリジンに/apiを付与したURLを使用
- AXIOS経由でREST APIを呼び出し
- REST APIのレスポンスでパスワードのステートを更新
- スナックバーの表示関数handleClickを追加しました。(49~55行目)
- パスワードをクリップボードにコピー
- 2秒間スナックバーを表示
- GenerateボタンのクリックイベントでgeneratePassword関数を呼び出すようにしました。(86行目)
- パスワードのステートの値が入っている場合に、パスワードの値を表示するようにしました。(96~99行目)
- パスワードの値をクリックイベントでhandleClick関数を呼び出し
また、これに合わせてApp.cssにスナックバー用のスタイルも追加しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
.snackbar { visibility: visible; min-width: 250px; margin-left: -125px; background-color: #777; color: #fff; text-align: center; border-radius: 2px; padding: 16px; position: fixed; z-index: 1; left: 50%; bottom: 30px; font-size: 17px; opacity: 0.9; transition: opacity 0.5s ease-in-out; } |
環境変数にVITE_API_URL_BASEを追加したので、.envファイルにも追加します。
1 |
VITE_API_URL_BASE=http://localhost:8080/api |
実行してみると、無事にAPIを呼び出すことができました😆
![]() |
ちなみに別途HTMLを作成して、このAPIを呼び出してもCORSにひっかかり実行できないことは確認しています。
今回思ったより記事が長くなってしまい、Cognito認証ユーザーだけが実行できるAPIの実装までたどり着きませんでしたので、次回はそれについて書きたいと思います!
それでは、また次回に👋
過去の記事
執筆者プロフィール

- tdi デジタルイノベーション技術部
-
昔も今も新しいものが大好き!
インフラからアプリまで縦横無尽にトータルサポートや新技術の探求を行っています。
週末はときどきキャンプ場に出没します。