SpringBootとNuxtJSでTODOアプリを作る


tektonでもう少し複雑なCI/CDフローを作ったり、別の基盤で動かしたりするサンプルとして、 フロントエンドとバックエンドにわかれたアプリを作ってみた。 とはいえ、あまり複雑にするつもりはなかったので、シンプルにTODOアプリにしている。

普段はGolangで書くことが多いのだけど、今回はフロントエンドに NuxtJS 、 バックエンドに SpringBoot を使ってみた。

アーキテクチャとしては、こんな感じ。 最初は VueJS で考えていたが、ユーザ側に公開するサービスは1つだけにしたかったので NuxtJS のほうがやりやすいかな、とこういう構成にしてみた。

/ox-hugo/20200822-architecture.png

ソースコードは以下に配置している。

https://github.com/grugrut/todo-springboot

基本的な作りはシンプルだが、やはり初めてさわるものであり、いくつかハマったところがあるので紹介。

バックエンド側のSpringBoot

本番・開発で設定をわける

DB接続先情報など、本番と開発で設定をわけたいものがある。 SpringBootでは、 application.propertiesspring.profile.active というプロパティで プロファイルを設定することができる。

これを設定しておくことで、 application.properties が読みこまれた後に、 application-(プロファイル名).properties が追加で読みこまれる。

こちらには環境変数が使えるので、

spring.profiles.active = ${ENV:dev}

のように、環境変数 ENV が設定されていたらそれをプロファイル名として読みこみ、 未設定の場合には、devが設定されるようにした。

未設定の場合のデフォルト値を設定したことで、開発環境では環境変数を設定しておく必要がないのが便利。

https://github.com/grugrut/todo-springboot/blob/5017ac9b720b8d1d7e6462a4ff439db0a9dfdd07/src/backend/src/main/resources/application.properties

開発時のテーブル作成

開発環境ではH2データベースを利用した。 H2データベースは、javaで書かれたRDBMSで、eclipseからも簡単に扱うことができる。

SpringBootでは、開発環境での実行時にDDLとDMLをそれぞれ実行することができ、 やはり application.properties で実行するsqlファイルを設定する。

spring.datasource.schema=classpath:schema.sql
spring.datasource.data=classpath:data.sql

初期データは空でよかったので、schema.sqlの実行だけでよかったのだが、 data.sqlを作成していなかったり作成しても空ファイルだとエラーが出てしまった。

そのため、しかたがなく SELECT 1; だけ実行する意味のない data.sql を作成して回避している。

これはさすがにもっと良いやり方があるのかと思うが、回避できてるのでヨシ!

org.springframework.context.ApplicationContextException: Unable to start web server; nested exception is org.springframework.boot.web.server.WebServerException: Unable to start embedded Tomcat
  at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:161) ~[spring-boot-2.3.2.RELEASE.jar:2.3.2.RELEASE]
  at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:545) ~[spring-context-5.2.8.RELEASE.jar:5.2.8.RELEASE]
  at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:143) ~[spring-boot-2.3.2.RELEASE.jar:2.3.2.RELEASE]
  at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:758) ~[spring-boot-2.3.2.RELEASE.jar:2.3.2.RELEASE]
  at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:750) ~[spring-boot-2.3.2.RELEASE.jar:2.3.2.RELEASE]
  at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397) ~[spring-boot-2.3.2.RELEASE.jar:2.3.2.RELEASE]
  at org.springframework.boot.SpringApplication.run(SpringApplication.java:315) ~[spring-boot-2.3.2.RELEASE.jar:2.3.2.RELEASE]
  at org.springframework.boot.SpringApplication.run(SpringApplication.java:1237) ~[spring-boot-2.3.2.RELEASE.jar:2.3.2.RELEASE]
  at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226) ~[spring-boot-2.3.2.RELEASE.jar:2.3.2.RELEASE]
  at net.grugrut.todo.TodoSpringApplication.main(TodoSpringApplication.java:10) ~[classes/:na]
  at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
  at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
  at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
  at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
  at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49) ~[spring-boot-devtools-2.3.2.RELEASE.jar:2.3.2.RELEASE]
Caused by: org.springframework.boot.web.server.WebServerException: Unable to start embedded Tomcat
  at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.initialize(TomcatWebServer.java:142) ~[spring-boot-2.3.2.RELEASE.jar:2.3.2.RELEASE]
  at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.<init>(TomcatWebServer.java:104) ~[spring-boot-2.3.2.RELEASE.jar:2.3.2.RELEASE]
  at org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getTomcatWebServer(TomcatServletWebServerFactory.java:437) ~[spring-boot-2.3.2.RELEASE.jar:2.3.2.RELEASE]
  at org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getWebServer(TomcatServletWebServerFactory.java:191) ~[spring-boot-2.3.2.RELEASE.jar:2.3.2.RELEASE]
  at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.createWebServer(ServletWebServerApplicationContext.java:178) ~[spring-boot-2.3.2.RELEASE.jar:2.3.2.RELEASE]
  at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:158) ~[spring-boot-2.3.2.RELEASE.jar:2.3.2.RELEASE]
  ... 14 common frames omitted
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'h2Console' defined in class path resource [org/springframework/boot/autoconfigure/h2/H2ConsoleAutoConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.boot.web.servlet.ServletRegistrationBean]: Factory method 'h2Console' threw exception; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSource' defined in class path resource [org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration$Hikari.class]: Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerInvoker': Invocation of init method failed; nested exception is org.springframework.jdbc.datasource.init.UncategorizedScriptException: Failed to execute database script from resource [URL [file:/git/todo-springboot/src/backend/target/classes/data.sql]]; nested exception is java.lang.IllegalArgumentException: 'script' must not be null or empty
  at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:655) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
  at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:635) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1336) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1176) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:556) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:516) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
  at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:324) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
  at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:226) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
  at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:322) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
  at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:207) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
  at org.springframework.boot.web.servlet.ServletContextInitializerBeans.getOrderedBeansOfType(ServletContextInitializerBeans.java:211) ~[spring-boot-2.3.2.RELEASE.jar:2.3.2.RELEASE]
  at org.springframework.boot.web.servlet.ServletContextInitializerBeans.getOrderedBeansOfType(ServletContextInitializerBeans.java:202) ~[spring-boot-2.3.2.RELEASE.jar:2.3.2.RELEASE]
  at org.springframework.boot.web.servlet.ServletContextInitializerBeans.addServletContextInitializerBeans(ServletContextInitializerBeans.java:96) ~[spring-boot-2.3.2.RELEASE.jar:2.3.2.RELEASE]
  at org.springframework.boot.web.servlet.ServletContextInitializerBeans.<init>(ServletContextInitializerBeans.java:85) ~[spring-boot-2.3.2.RELEASE.jar:2.3.2.RELEASE]
  at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.getServletContextInitializerBeans(ServletWebServerApplicationContext.java:255) ~[spring-boot-2.3.2.RELEASE.jar:2.3.2.RELEASE]
  at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.selfInitialize(ServletWebServerApplicationContext.java:229) ~[spring-boot-2.3.2.RELEASE.jar:2.3.2.RELEASE]
  at org.springframework.boot.web.embedded.tomcat.TomcatStarter.onStartup(TomcatStarter.java:53) ~[spring-boot-2.3.2.RELEASE.jar:2.3.2.RELEASE]
  at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5128) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
  at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
  at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1384) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
  at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1374) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
  at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) ~[na:na]
  at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
  at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:140) ~[na:na]
  at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:909) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
  at org.apache.catalina.core.StandardHost.startInternal(StandardHost.java:841) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
  at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
  at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1384) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
  at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1374) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
  at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) ~[na:na]
  at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
  at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:140) ~[na:na]
  at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:909) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
  at org.apache.catalina.core.StandardEngine.startInternal(StandardEngine.java:262) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
  at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
  at org.apache.catalina.core.StandardService.startInternal(StandardService.java:421) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
  at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
  at org.apache.catalina.core.StandardServer.startInternal(StandardServer.java:930) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
  at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
  at org.apache.catalina.startup.Tomcat.start(Tomcat.java:486) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
  at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.initialize(TomcatWebServer.java:123) ~[spring-boot-2.3.2.RELEASE.jar:2.3.2.RELEASE]
  ... 19 common frames omitted
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.boot.web.servlet.ServletRegistrationBean]: Factory method 'h2Console' threw exception; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSource' defined in class path resource [org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration$Hikari.class]: Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerInvoker': Invocation of init method failed; nested exception is org.springframework.jdbc.datasource.init.UncategorizedScriptException: Failed to execute database script from resource [URL [file:/git/todo-springboot/src/backend/target/classes/data.sql]]; nested exception is java.lang.IllegalArgumentException: 'script' must not be null or empty
  at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
  at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:650) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
  ... 59 common frames omitted
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSource' defined in class path resource [org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration$Hikari.class]: Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerInvoker': Invocation of init method failed; nested exception is org.springframework.jdbc.datasource.init.UncategorizedScriptException: Failed to execute database script from resource [URL [file:/git/todo-springboot/src/backend/target/classes/data.sql]]; nested exception is java.lang.IllegalArgumentException: 'script' must not be null or empty
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:602) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:516) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
  at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:324) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
  at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:226) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
  at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:322) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
  at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
  at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
  at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1307) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
  at org.springframework.beans.factory.support.DefaultListableBeanFactory$DependencyObjectProvider.getIfAvailable(DefaultListableBeanFactory.java:1947) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
  at org.springframework.beans.factory.ObjectProvider.ifAvailable(ObjectProvider.java:91) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
  at org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration.h2Console(H2ConsoleAutoConfiguration.java:72) ~[spring-boot-autoconfigure-2.3.2.RELEASE.jar:2.3.2.RELEASE]
  at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
  at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
  at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
  at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
  at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
  ... 60 common frames omitted
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerInvoker': Invocation of init method failed; nested exception is org.springframework.jdbc.datasource.init.UncategorizedScriptException: Failed to execute database script from resource [URL [file:/git/todo-springboot/src/backend/target/classes/data.sql]]; nested exception is java.lang.IllegalArgumentException: 'script' must not be null or empty
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1794) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:594) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:516) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
  at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:324) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
  at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:226) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
  at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:322) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
  at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:227) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
  at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1175) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
  at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveBean(DefaultListableBeanFactory.java:420) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
  at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:350) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
  at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:343) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
  at org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerPostProcessor.postProcessAfterInitialization(DataSourceInitializerPostProcessor.java:52) ~[spring-boot-autoconfigure-2.3.2.RELEASE.jar:2.3.2.RELEASE]
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:430) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1798) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:594) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
  ... 75 common frames omitted
Caused by: org.springframework.jdbc.datasource.init.UncategorizedScriptException: Failed to execute database script from resource [URL [file:/git/todo-springboot/src/backend/target/classes/data.sql]]; nested exception is java.lang.IllegalArgumentException: 'script' must not be null or empty
  at org.springframework.jdbc.datasource.init.ScriptUtils.executeSqlScript(ScriptUtils.java:645) ~[spring-jdbc-5.2.8.RELEASE.jar:5.2.8.RELEASE]
  at org.springframework.jdbc.datasource.init.ResourceDatabasePopulator.populate(ResourceDatabasePopulator.java:254) ~[spring-jdbc-5.2.8.RELEASE.jar:5.2.8.RELEASE]
  at org.springframework.jdbc.datasource.init.DatabasePopulatorUtils.execute(DatabasePopulatorUtils.java:49) ~[spring-jdbc-5.2.8.RELEASE.jar:5.2.8.RELEASE]
  at org.springframework.boot.autoconfigure.jdbc.DataSourceInitializer.runScripts(DataSourceInitializer.java:202) ~[spring-boot-autoconfigure-2.3.2.RELEASE.jar:2.3.2.RELEASE]
  at org.springframework.boot.autoconfigure.jdbc.DataSourceInitializer.initSchema(DataSourceInitializer.java:119) ~[spring-boot-autoconfigure-2.3.2.RELEASE.jar:2.3.2.RELEASE]
  at org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerInvoker.initialize(DataSourceInitializerInvoker.java:75) ~[spring-boot-autoconfigure-2.3.2.RELEASE.jar:2.3.2.RELEASE]
  at org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerInvoker.afterPropertiesSet(DataSourceInitializerInvoker.java:65) ~[spring-boot-autoconfigure-2.3.2.RELEASE.jar:2.3.2.RELEASE]
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1853) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1790) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
  ... 89 common frames omitted
Caused by: java.lang.IllegalArgumentException: 'script' must not be null or empty
  at org.springframework.util.Assert.hasText(Assert.java:287) ~[spring-core-5.2.8.RELEASE.jar:5.2.8.RELEASE]
  at org.springframework.jdbc.datasource.init.ScriptUtils.splitSqlScript(ScriptUtils.java:214) ~[spring-jdbc-5.2.8.RELEASE.jar:5.2.8.RELEASE]
  at org.springframework.jdbc.datasource.init.ScriptUtils.executeSqlScript(ScriptUtils.java:592) ~[spring-jdbc-5.2.8.RELEASE.jar:5.2.8.RELEASE]
  ... 97 common frames omitted

フロントエンド側のNuxtJS

本番・開発で設定をわける

バックエンドと同じくフロントエンド側も、本番では環境変数をあたえることで 設定をわけるようにしたい。

NuxtJSの場合は、 nuxt.config.js で設定することができる。

proxy: {
  '/api/': {
    target: process.env.API_BASE_URL || 'http://localhost:8080',
    pathRewrite: {'^/api/': ''}
  }
},

こんな感じ。他のソース中での変数参照として、 process.env.ENV_VALUE を上書きする方法もあるようだったが、 今回の範囲では利用するシーンがなかったので省略。

https://github.com/grugrut/todo-springboot/blob/5017ac9b720b8d1d7e6462a4ff439db0a9dfdd07/src/frontend/nuxt.config.js

NuxtJSでサーバサイドをバックエンド呼びだしのプロキシとして利用する

本来であればjavascriptなので、そこからのAPI呼びだしはクライアント(つまり利用者のブラウザ)から バックエンドのAPIサーバへの直接の呼びだしになる。

しかし、バックエンドのエンドポイントを外にだしたくない場合、NuxtJSのサーバサイドでも動いている特性を活かして、 NuxtJSのサーバ側をリバースプロキシとして利用することができる。

その実現のために、axiosのproxy機能を利用する。

サーバサイドでaxiosを利用するために、 npm install --save @nuxtjs/axios して nuxt.config.js に以下の設定をすればよい。

modules: [
  "@nuxtjs/axios",
],
axios: {
  proxy: true
},
proxy: {
  '/api/': {
    // for WSL2, `API_BASE_URL=http://$(cat /etc/resolv.conf | grep nameserver | awk '{print $2}'):8080 npm run dev`
    target: process.env.API_BASE_URL || 'http://localhost:8080',
    pathRewrite: {'^/api/': ''}
  }
},

これにより、この設定の場合は /api/xxx に対するアクセスが、 サーバ側で動いているExpress経由で target/xxx にアクセスできる。 そのため、バックエンドのエンドポイントはフロントエンドのプロセスからのみアクセスできればよく、 クライアントにまで公開しておく必要がない。

あちこちのスラッシュの有無で、期待通りに動かなかったので、そこが難しかった。

サーバサイドとクライアントサイドでのVuexストアの扱い

今回はTODOアプリを作っているので、TODOリストの各アイテムの情報をVuexストアに格納して、 ユーザ操作に応じて最終的なDBとの同期をとっている。

本来であれば、サーバサイドで一度Vuexストアに情報を格納したあと、 以降はクライアントサイドでいい感じに扱いたかったのだが、うまくいかなかったので Vuexストアは結局すべてクライアントサイドに寄せている。

詳細は以下のふたつのソースを見たほうが確実だと思う。

https://github.com/grugrut/todo-springboot/blob/5017ac9b720b8d1d7e6462a4ff439db0a9dfdd07/src/frontend/pages/index.vue

https://github.com/grugrut/todo-springboot/blob/5017ac9b720b8d1d7e6462a4ff439db0a9dfdd07/src/frontend/store/index.js

このVuexストアの書きかたは、すでに非推奨になっているので書きかえが必要だが。

まとめ

フロントエンドをNuxtJS、バックエンドをSpringBootとするシンプルなTODOアプリができた。 このあとは、コンテナ化してkubernetesなどで動かす予定。


See also

comments powered by Disqus