Solid frontends: configuration

The 12-factor application manifesto made a significant contribution to the development and operation of web applications, but this mostly affected backends, and bypassed the front-ends. Most of the clauses in the manifest are either not applicable to frontends, or they are performed by themselves, but with number 3 - configuration - there are questions.





The original manifest said, "Keep the configuration at runtime." In practice, this means that the configuration cannot be stored inside the source code or the final artifact. It needs to be passed to the application at startup time. There are practical uses for this rule, here are a couple:





  1. An application in different environments must access different backends. On production - to production API, on staging - to API staging, and when running integration tests - to a special mock server.





  2. For e2e tests, it is necessary to reduce the waiting time for the user's reaction. For example, if something happens on the site after 10 minutes of inactivity, then for a test scenario you can reduce this interval to a minute.





SSR

If the front-end application contains SSR, then the task becomes a little easier. The configuration is passed as environment variables to the application on the server; when rendered, it gets into the response to the client as global variables declared at <script>



the very beginning of the page. On the client, it is enough to pick up these variables from the global scope and use them.





Recently, at Aviasales, we made an application for server rendering of parts of the site and were faced with this problem. The result is my teammate zaopensorsil - isomorphic-env-webpack-plugin .





The beautiful Next.js can do this out of the box , you don't need to do anything special.





CSR

, — . , , . , .





:





  1. config.js



    , . , . — -, -, — -. — PR .





  2. . DefinePlugin



    Webpack. — . — , . , , . .





.





-

- :





  1. nginx, nginx -. nginx .





  2. , — API.





— , nginx . /user-api/path



https://user.my-service.io/path



, /auth-api/path



https://auth.other-service.io/path



.





nginx Docker-





1.19 Docker- nginx . .template



/etc/nginx/templates



. , .





nginx SPA :





server {
  listen   8080;

  root /srv/www;
  index index.html;
  server_name _;

  location /user-api {
    proxy_pass ${USER_API_URL};
  }

  location /auth-api {
    proxy_pass ${AUTH_API_URL};
  }

  location / {
    try_files $uri /index.html;
  }
}
      
      



Dockerfile :





FROM node:14.15.0-alpine as build

WORKDIR /app
#   
# ...

FROM nginx:1.19-alpine

COPY ./default.conf.template /etc/nginx/templates/default.conf.template

COPY --from=build /app/public /srv/www
EXPOSE 8080
      
      



, nginx .





, , .





.





. Caddy , Traefik .





API, - .





-, . , .





JS-:





window.__ENV__ = {
  USER_API_URL: 'https://user.my-service.io/',
  AUTH_API_URL: 'https://auth.other-service.io/',
};

      
      



, . . <script>



HTML- .





nginx Docker-





, , — API, . , , env.dict



:





BACK_URL
GOOGLE_CLIENT_ID
      
      



Bash- generate_env.sh



JS-:





#!/bin/bash
filename='/etc/nginx/env.dict'

#  JS-
config_str="window._env_ = { "

#    JS-
while read line; do
variable_str="${line}: \"${!line}\""
config_str="${config_str}${variable_str}, "
done < $filename

#  JS-
config_str="${config_str} };"

#    
echo "Creating config-file with content: \"${config_str}\""
echo "${config_str}" >> /srv/www/config.env.js

#  <script>    HTML-
sed -i '/<\/body><\/html>/ i <script src="/confit.env.js"></script>' *.html
      
      



Bash, . , .





nginx , nginx. cmd.sh



, :





#!/bin/bash

bash /etc/nginx/generate_env.sh

nginx -g "daemon off;"
      
      



Dockerfile:





FROM node:14.15.0-alpine as build

WORKDIR /app
#   
# ...

FROM nginx:1.19-alpine

#    Alpine  Bash,  
RUN apk add bash

COPY ./default.conf /etc/nginx/conf.d/

COPY --from=build /app/public /srv/www

COPY ./cmd.sh /etc/nginx/cmd.sh
COPY ./generate_env.sh /etc/nginx/generate_env.sh
COPY ./env.dict /etc/nginx/env.dict
 
EXPOSE 8080

CMD ["bash", "/etc/nginx/cmd.sh"]
      
      



env.dict



.





.





, , SSR . isomorphic-env-webpack-plugin, : HTML.





There is one more small edge case in this scheme - usually a content hash is added to the names of files with resources so that in the browser all files can be cached forever by name without any problems. In this case, you need to slightly complicate the script for generating a file with variables, hash the contents and add the result to the file name.





Conclusion

Correct work with the parameters of the front-end application helps to create reliable and easy-to-use systems. It is enough to decouple the configuration from the application and move it to the runtime to radically improve the comfort of team members and reduce potential errors.





How do you pass configs to client applications?








All Articles