Skip to content

Commit

Permalink
feature: add port binding for backend and frontend
Browse files Browse the repository at this point in the history
  • Loading branch information
MDeLuise committed Sep 8, 2023
1 parent fb7d32d commit 5dfb01b
Show file tree
Hide file tree
Showing 13 changed files with 125 additions and 107 deletions.
75 changes: 56 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,58 +49,84 @@ There are two different images for the service:
* `msdeluise/plant-it-frontend`

This images can be use indipendently, or they can be use in a docker-compose file.
For the sake of simplicity, the provided docker-compose.yml file is reported here:
For the sake of simplicity, the provided `docker-compose.yml` file is reported here:
```
version: "3"
name: plant-it
services:
backend:
image: msdeluise/plant-it-backend:latest
env_file: backend.env
depends_on:
- db
- cache
restart: unless-stopped
volumes:
- "./upload-dir:/upload-dir"
ports:
- "8080:8080"
db:
image: mysql:8.0
restart: always
env_file: backend.env
cache:
image: redis:7.2.1
restart: always
frontend:
image: msdeluise/plant-it-frontend:latest
env_file: frontend.env
links:
- backend
reverse-proxy:
image: nginx:stable-alpine
ports:
- "8080:80"
volumes:
- ./default.conf:/etc/nginx/conf.d/default.conf
links:
- backend
- frontend
- "3000:3000"
```

Run the docker compose file (`docker compose -f <file> up -d`), then the service will be available at `localhost:8080`, while the REST API will be available at `localhost:8080/api` (`localhost:8080/api/swagger-ui/index.html` for the documentation of them).

<details>

<summary>Run on a remote host</summary>
> ℹ️ *Run on a remote host (_e.g. run the system in a server and access it from mobile_)*
>
> Please notice that running the `docker-compose` file on a machine and connect to the system from another machine change the way to connect to the server.
>
> For example, if you run the `docker-compose` on the machine with the local IP `192.168.1.100` then you have to change the backend url in the [API_URL](#configuration) (`frontend.env` file) parameter to `http://192.168.1.100:8080/api`. In this case, the frontend of the system will be available at `http://192.168.1.100:3000`, and the backend will be available at `http://192.168.1.100:8080/api`.
>
> Why this mandatory changes? [See here](#dns-and-cors).

Please notice that running the `docker-compose` file from another machine change the way to connect to the server. For example, if you run the `docker-compose` on the machine with the local IP `192.168.1.100` then you have to change the backend url in the [API_URL](#configuration) variable to `http://192.168.1.100:8080/api`. In this case, the frontend of the system will be available at `http://192.168.1.100:8080`, and the backend will be available at `http://192.168.1.100:8080/api`.
</details>
#### Change port binding
##### Backend
If you don't want to use the default port `8080`, you can follow these steps:
* change the port binding in the `docker-compose.yml` file, e.g. `9090:8080` to setup the port `9090` for the backend service
* update the [API_URL](#configuration) variable in order to points to the correct backend address
##### Frontend
If you don't want to use the default port `3000`, you can follow these steps:
* change the port binding in the `docker-compose.yml` file, e.g. `4040:3000` to setup the port `4040` for the frontend service

#### DNS and CORS
##### DNS
If you are asking yourself why it's not possibile to simply use `backend` and `frontend` hostnames instead of the IPs in the [API_URL](#configuration) and in the [ALLOWED_ORIGINS](#configuration) variables, here's the problem.

When the JavaScript runs in a browser (outside of Docker) you can not use service hostnames because they are only available inside the Docker network (via the embedded DNS server) [<sup>[1]</sup>](https://stackoverflow.com/questions/46080290/reactjs-browser-app-cannot-see-things-in-the-docker-compose-network) [<sup>[2]</sup>](https://stackoverflow.com/questions/70770619/dockerized-react-app-axios-req-to-backend-doesnt-work?rq=3).

In a more practical way:
* The browser you're using to access the app have no knowledge of what `backend` is. This leads to the error `ERR_NAME_NOT_RESOLVED` if trying to use `http://backend:8080` as value for the property `API_URL` (`frontend.env` file).
* The backend will not receives request from the `frontend` service (the container), it will receive them from the browser you're using (the client). So if you try to use `http://frontend:3000` as value for the property `ALLOWED_ORIGINS` (`backend.env` file) it will not works.
* The use of `localhost` hostname also does not fix the problem in those cases where you access the app from another device (e.g. the system is deployed on a server and you access it via mobile)

##### CORS
Given the above, you can change the value of the `ALLOWED_ORIGINS` parameter (`backend.env` file) in order to be more strict than the default `*`. However, keep in mind that there you have to put the IPs from which you will access the system (i.e. the client/brower you're using and the REST API client if any).

### Setup without docker
The application was developed with being used with Docker in mind, thus this method is not preferred.

#### Requirements
* [JDK 19+](https://openjdk.org/)
* [JDK 20+](https://openjdk.org/)
* [MySQL](https://www.mysql.com/)
* [React](https://reactjs.org/)
* [ReactJS](https://reactjs.org/)

#### Run
1. Be sure to have the `mysql` database up and running
Expand All @@ -112,8 +138,16 @@ The application was developed with being used with Docker in mind, thus this met
Then, the frontend of the system will be available at `http://localhost:3000`, and the backend at `http://localhost:8085/api`.


## Configuration
#### Change port binding
If you don't want to use the default ports (`3000` for the frontend and `8080` for the backend), you can modify the following [configuration properties](#configuration):
* in the `backend.env` file:
* `API_PORT`: port to serve the backend
* in the `frontend.env` file:
* `PORT`: port to serve the frontend
* `API_URL`: address of the API, e.g. `http//localhost:<API_PORT>/api`


## Configuration
There are 2 configuration file available:
* `deployment/backend.env`: file containing the configuration for the backend. An example of content is the following:
```
Expand All @@ -128,19 +162,22 @@ There are 2 configuration file available:
JWT_EXP=1
USERS_LIMIT=-1 # including the admin account, so <= 0 if undefined, >= 2 if defined
UPLOAD_DIR= # path to the directory used to store uploaded images, if on docker deployment leave as it is and change the volume binding if needed
UPLOAD_DIR=/upload-dir # path to the directory used to store uploaded images, if on docker deployment leave as it is and change the volume binding in the docker-compose file if needed
API_PORT=8080
CACHE_TTL=86400
CACHE_HOST=cache
CACHE_PORT=6379
TRAFLE_KEY= # put you key here, otherwise the "search" feature will include only user generated species
FRONTEND_URL=http://localhost:3000 # CORS allowed origin
ALLOWED_ORIGINS=* # CORS allowed origins (comma separated list, (syntax)[#])
```
Change the properties values according to your system.

* `deployment/frontend.env`: file containing the configuration for the frontend. An example of content is the following:
```
PORT=3000 # port that will serve the frontend, if on docker deployment leave as it is and change the port binding in the docker-compose file if needed
API_URL=http://localhost:8080/api
PAGE_SIZE=25
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package com.github.mdeluise.plantit.security;

import java.util.Arrays;

import com.github.mdeluise.plantit.security.apikey.ApiKeyFilter;
import com.github.mdeluise.plantit.security.jwt.JwtTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
Expand Down Expand Up @@ -103,8 +104,8 @@ public SecurityFilterChain configure(HttpSecurity http) throws Exception {


@Bean
@Profile("dev")
public WebMvcConfigurer corsConfigurer(@Value("${server.cors.allowed-origins}") String allowedOrigins) {
System.out.println(Arrays.toString(allowedOrigins.split(",")));
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
Expand Down
4 changes: 2 additions & 2 deletions backend/src/main/resources/application-dev.properties
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,9 @@ jwt.cookie.name = plant-it
#
# Server config
#
server.port = 8085
server.port = ${API_PORT:8085}
server.servlet.context-path = /api
server.cors.allowed-origins = ${FRONTEND_URL:http://localhost:3000}
server.cors.allowed-origins = ${ALLOWED_ORIGINS:http://localhost:3000}


#
Expand Down
8 changes: 4 additions & 4 deletions backend/src/main/resources/application-integration.properties
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ jwt.cookie.name = plant-it
#
# Server config
#
server.port = 8090
server.port = ${API_PORT:8090}
server.address = 0.0.0.0
server.cors.allowed-origins = ${FRONTEND_URL:http://localhost:3000}
server.cors.allowed-origins = ${ALLOWED_ORIGINS:http://localhost:3000}


#
Expand All @@ -64,9 +64,9 @@ app.version = @project.version@
#
# System config
#
users.max = -1
users.max = ${USERS_LIMIT:-1}
trefle.key =
upload.location = /tmp/plant-it
upload.location = ${UPLOAD_DIR:/tmp/plant-it}


#
Expand Down
4 changes: 2 additions & 2 deletions backend/src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ jwt.cookie.name = plant-it
#
# Server config
#
server.port = 8080
server.port = ${API_PORT:8080}
server.servlet.context-path = /api
server.cors.allowed-origins = ${FRONTEND_URL:http://localhost:3000}
server.cors.allowed-origins = ${ALLOWED_ORIGINS:http://localhost:3000}


#
Expand Down
5 changes: 4 additions & 1 deletion deployment/backend.env
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@ JWT_SECRET=putTheSecretHere
JWT_EXP=1

USERS_LIMIT=2 # including the admin account, so <= 0 if undefined, >= 2 if defined
UPLOAD_DIR=/upload-dir
API_PORT=8080

CACHE_TTL=86400
CACHE_HOST=cache
CACHE_PORT=6379

FRONTEND_URL=http://localhost:3000
TRAFLE_KEY=

ALLOWED_ORIGINS=*
51 changes: 0 additions & 51 deletions deployment/default.conf

This file was deleted.

16 changes: 5 additions & 11 deletions deployment/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,22 @@ services:
restart: unless-stopped
volumes:
- "./upload-dir:/upload-dir"
ports:
- "8080:8080"

db:
image: mysql:latest
image: mysql:8.0
restart: always
env_file: backend.env

cache:
image: redis:latest
image: redis:7.2.1
restart: always

frontend:
image: msdeluise/plant-it-frontend:latest
env_file: frontend.env
links:
- backend

reverse-proxy:
image: nginx:stable-alpine
ports:
- "8080:80"
volumes:
- ./default.conf:/etc/nginx/conf.d/default.conf
links:
- backend
- frontend
- "3000:3000"
2 changes: 1 addition & 1 deletion deployment/frontend.env
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
PORT=3000
API_URL=http://localhost:8080/api

PAGE_SIZE=25

BROWSER=none

4 changes: 3 additions & 1 deletion frontend/.env
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
PORT=3000
API_URL=http://localhost:8085/api
PAGE_SIZE=25

PAGE_SIZE=25
3 changes: 2 additions & 1 deletion frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ export function App() {

axiosReq.interceptors.request.use(
(req) => {
if (!req.url?.startsWith("authentication") && !req.url?.startsWith("api-key")) {
if (!req.url?.startsWith("authentication") && !req.url?.startsWith("api-key") &&
!req.url?.startsWith("info")) {
req.headers['Key'] = secureLocalStorage.getItem("plant-it-key");
}
return req;
Expand Down
10 changes: 9 additions & 1 deletion frontend/src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,12 @@ const setAbsoluteImageUrl = (requestor: AxiosInstance, publicUrl: string, imageU

export const getBotanicalInfoImg = (requestor: AxiosInstance, imageUrl?: string): Promise<string> => {
return setAbsoluteImageUrl(requestor, process.env.PUBLIC_URL, imageUrl);
};
};

export const isBackendReachable = (requestor: AxiosInstance): Promise<boolean> => {
return new Promise((resolve, _reject) => {
requestor.get("/info/ping")
.then((_res) => resolve(true))
.catch((_err) => resolve(false));
});
};
Loading

0 comments on commit 5dfb01b

Please sign in to comment.