- For IR usage see https://github.com/JetBrains/kotlin-wrappers#experimental-ir-backend
To run frontend locally, use ./gradlew :save-frontend:browserDevelopmentRun --continuous or ./gradlew :save-frontend:browserProductionRun --continuous.
To pack distribution, use ./gradlew :save-frontend:browserDevelopmentWebpack and ./gradlew :save-frontend:browserProductionWebpack.
save-backend consumes frontend distribution as a dependency. Frontend distribution is copied and included in spring boot resources.
The most interesting part of nginx.conf is here:
location / {
index index.html;
try_files $uri $uri/ /index.html;
add_header Cache-Control "private; no-store";
etag off;
add_header Last-Modified "";
if_modified_since off;
}Here we define a configuration for the location / block, which is the default location block in nginx and matches any
request that doesn't match other specific location blocks.
Let's break down each directive and explain what happens:
index index.html; This directive sets the default file to serve when a directory is requested. In this case, if a request is made to a directory (e.g., http://example.com/), Nginx will try to serve theindex.htmlfile from that directory. If the file doesn't exist, Nginx will move to the next directive.try_files $uri $uri/ /index.html;This directive specifies a series of files and locations that Nginx should try to serve. It acts as a fallback mechanism when the requested file is not found in the specified locations.
$urirepresents the requested URI.$uri/represents the URI followed by a trailing slash, typically used for directories./index.htmlis the last fallback option.
add_header Cache-Control "private; no-store";This directive adds an HTTP response header namedCache-Controlto the server's responses."private; no-store"sets theCache-Controlheader value, which tells the client and intermediate caching servers not to store any cached copies of the response. It ensures that the content is not stored in any cache, making every request hit the server directly.etag off;ETagis an identifier that helps with caching, but disabling it ensures that the server doesn't useETagfor caching validation. This can be useful for certain scenarios whereETagsare not necessary or could cause caching issues.add_header Last-Modified "";TheLast-Modifiedheader is used for caching validation, but setting it to an empty value indicates that the server does not want to participate in any caching validation based on the last modification date of the resource.if_modified_since off;Theif_modified_sincedirective controls the behavior of theIf-Modified-Sincerequest header. Disabling it ensures that clients won't use conditional requests, which can be helpful in certain caching scenarios or when caching behavior needs to be controlled explicitly.
Here is a webpack-dev-server configuration for running without api-gateway on:
config.devServer = Object.assign(
{},
config.devServer || {},
{
setupMiddlewares: (middlewares, devServer) => {
devServer.app.get("/sec/oauth-providers", (req, res) => { return res.send([]); });
return middlewares;
},
proxy: [
{
context: ["/api/demo/**"],
target: 'http://localhost:5421',
logLevel: 'debug',
onProxyReq: function (proxyReq, req, res) {
proxyReq.setHeader("X-Authorization-Id", "1");
proxyReq.setHeader("X-Authorization-Name", "admin");
proxyReq.setHeader("X-Authorization-Roles", "ROLE_SUPER_ADMIN");
proxyReq.setHeader("X-Authorization-Status", "ACTIVE");
}
},
{
context: ["/api/cpg/**"],
target: 'http://localhost:5500',
logLevel: 'debug',
onProxyReq: function (proxyReq, req, res) {
proxyReq.setHeader("X-Authorization-Id", "1");
proxyReq.setHeader("X-Authorization-Name", "admin");
proxyReq.setHeader("X-Authorization-Roles", "ROLE_SUPER_ADMIN");
proxyReq.setHeader("X-Authorization-Status", "ACTIVE");
}
},
{
context: ["/api/**"],
target: 'http://localhost:5800',
logLevel: 'debug',
onProxyReq: function (proxyReq, req, res) {
proxyReq.setHeader("X-Authorization-Id", "1");
proxyReq.setHeader("X-Authorization-Name", "admin");
proxyReq.setHeader("X-Authorization-Roles", "ROLE_SUPER_ADMIN");
proxyReq.setHeader("X-Authorization-Status", "ACTIVE");
}
}
],
historyApiFallback: true
}
);setupMiddlewares sets a stub for /sec/oauth-providers endpoint.
historyApiFallback makes webpack-dev-server return index.html in case of 404 Not Found.
Sometimes it is useful to add authorization headers when proxying to some Spring services e.g. save-backend.
It can be done with setting onProxyReq:
onProxyReq: (proxyReq, req, res) => {
proxyReq.setHeader("X-Authorization-Id", "1");
proxyReq.setHeader("X-Authorization-Name", "admin");
proxyReq.setHeader("X-Authorization-Roles", "ROLE_SUPER_ADMIN");
proxyReq.setHeader("X-Authorization-Status", "ACTIVE");
}Thus, we add Authorization and X-Authorization-Source headers that correspond with admin user headers.
-
When the default
dev-server.jsis used, the front-end is expected to communicate directly with the back-end, omitting any gateway. When enabling OAuth, make sure the gateway is contacted instead:context: add/sec/**, /oauth2/**, /login/oauth2/**to the list;target: change tohttp://localhost:5300(the default gateway URL);onProxyReq: drop the entire callback, since all auth headers (X-Authorization-Id,X-Authorization-Name,X_Authorization-StatusandX-Authorization-Roles) will be set by the gateway now (the gateway acts as a reverse proxy);bypass: drop the entire callback.
The resulting
dev-server.jsshould look like this:config.devServer = Object.assign( {}, config.devServer || {}, { proxy: [ { context: ["/api/**", "/sec/**", "/oauth2/**", "/logout/**", "/login/oauth2/**"], target: 'http://localhost:5300', logLevel: 'debug', } ], historyApiFallback: true } )
Notice that
historyApiFallbackis required forBrowserRouterwork fine. -
Avoid potential name conflicts between local users (those authenticated using HTTP Basic Auth) and users created via an external OAuth provider. For example, if you have a local user named
torvalds, don't try to authenticate as a GitHub user with the same name.