Packaging CRA on Spring Boot

3 minute read

Packaging CRA on Spring Boot๐Ÿฅ•

Written By Jun Park, VCANUS

1๊ฐœ์˜ Spring Boot ์„œ๋ฒ„์— REST API ์„œ๋ฒ„์™€ React App WAS ์„œ๋ฒ„๋ฅผ ํฌํ•จํ•˜๋Š” ๋ฐฉ๋ฒ•

๐Ÿ“Œ Spring Boot Setup

1. Init Spring Boot Project

  • spring initializr์—์„œ ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ
  • ์ด ๊ธ€์—์„œ ์‚ฌ์šฉํ•˜๋Š” ์„ค์ •
    • Project: Maven Project
    • Language: Java
    • Spring Boot: 2.4.5(as default)
    • Project Metadata
      • Packaging: Jar
      • Java: 8
    • Dependencies
      • Spring Web
      • Rest Repositories
      • Spring Data JPA
      • H2 Database

spring initializr

2. Rest API ๊ฐœ๋ฐœ

  • application.properties์— Rest API base path ์ถ”๊ฐ€
// src/main/resources/application.properties
// root๋Š” React๋ฅผ ํ˜ธ์ŠคํŠธํ•  ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์—
// Rest API์˜ root๋ฅผ /api๋กœ ์„ค์ •ํ•˜์—ฌ /api ์ดํ•˜ ์š”์ฒญ์€ Rest ์š”์ฒญ์œผ๋กœ ์ฒ˜๋ฆฌ

spring.data.rest.base-path=/api
// ๋˜๋Š” ์ˆ˜๋™์œผ๋กœ ๊ฐ ์š”์ฒญ๋งˆ๋‹ค ์ง€์ •
// ์ด ๊ธ€์€ base-path๋ฅผ ์„ค์ •ํ•œ ์ƒ์œ„ ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•œ๋‹ค๊ณ  ๊ฐ€์ •

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ProductController {

    private final ProductService service;

    @Autowired
    public ProductController(ProductService service) {
        this.service = service;
    }

    @GetMapping("/api/product/1") // ์ฃผ์†Œ๋ฅผ ๋ช…์‹œ
    public String selectProduct() {
        return service.select(1);
    }

    // ๊ทธ ์™ธ CRUD Mapping...
}
  • Entity, Repository ๊ฐœ๋ฐœ
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import java.util.Objects;

@Entity
public class User {

    private @Id @GeneratedValue Long id;
    private String name;

    // ๊ทธ ์™ธ constructor, getter, setter...
}
import org.springframework.data.repository.CrudRepository;

public interface UserRepository extends CrudRepository<User, Long> {}
  • Repository ~ Database ์—ฐ๊ฒฐ
// ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•œ ๋ฐ๋ชจ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์‚ฌ์šฉ

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class DatabaseLoader implements CommandLineRunner {

    private final UserRepository repository;

    @Autowired
    public DatabaseLoader(UserRepository repository) {
        this.repository = repository;
    }

    @Override
    public void run(String... args) throws Exception {
        this.repository.save(new User("Paul"));
        this.repository.save(new User("Tom"));
    }
}
  • ํ…Œ์ŠคํŠธ
// after run spring boot app

$ curl localhost:8080/api/users
// Result: two users, Paul and Tom

๐Ÿ“Œ React Setup

1. React ์ƒ์„ฑ

  • Spring boot project root์—์„œ Create React App
$ npx create-react-app frontend

2. React ๊ฐœ๋ฐœ

  • API ์š”์ฒญ
// npm install axios OR yarn add axios
import axios from 'axios';

const Users = () => {

  const [users, setUsers] = useState();

  useEffect(() => {
    axios.get('/api/employees')
      .then(response => setUsers(response.data._embedded.employees));
    return () => setUsers([])
  }, []);

  return (
    <React.Fragment>
      <h2>Users</h2>
      <UserList users={users} />
    </React.Fragment>
  );
}

3. ํ…Œ์ŠคํŠธ

  • ํ…Œ์ŠคํŠธ์šฉ proxy ์„ค์ •
// frontend/src/**/setupProxy.js
// npm install http-proxy-middleware OR yarn add http-proxy-middleware
const { createProxyMiddleware } = require('http-proxy-middleware');

// npm start ์‹œ ํฌํŠธ๋Š” 3000์œผ๋กœ tomcat ํฌํŠธ 8080์— API ์š”์ฒญ ์‹œ CORS ์œ„๋ฐ˜
// '/api' ํ•˜์œ„ ์ฃผ์†Œ๋กœ ์š”์ฒญ ์‹œ 8080์œผ๋กœ ์ž„์‹œ ์ „ํ™˜
// ๋ฐฐํฌ ์‹œ์—๋Š” ๊ฐ™์€ tomcat ์„œ๋ฒ„ ๋‚ด์—์„œ ์š”์ฒญํ•˜๊ธฐ์— ๋ฌด๊ด€
module.exports = (app) => {
    app.use(
        createProxyMiddleware('/api', {target: 'http://127.0.0.1:8080'})
    );
};
  • ํ…Œ์ŠคํŠธ
$ npm start

user-api-result

๐Ÿ“Œ Packaging

1. pom.xml ์ˆ˜์ •

  • frontend-maven-plugin ์ถ”๊ฐ€
<plugins>
...
    <plugin>
        <groupId>com.github.eirslett</groupId>
        <artifactId>frontend-maven-plugin</artifactId>
        <!-- Use the latest released version:
        https://repo1.maven.org/maven2/com/github/eirslett/frontend-maven-plugin/ -->
        <version>1.11.3</version>
        <configuration>
            <workingDirectory>frontend</workingDirectory>
            <installDirectory>target</installDirectory>
        </configuration>
        <executions>
            <execution>
                <id>install node and npm</id>
                <goals>
                    <goal>install-node-and-npm</goal>
                </goals>
                <configuration>
                    <!-- node.js, npm version -->
                    <nodeVersion>v14.16.1</nodeVersion>
                    <npmVersion>7.10.0</npmVersion>
                </configuration>
            </execution>
            <execution>
                <id>npm install</id>
                <goals>
                    <goal>npm</goal>
                </goals>
                <configuration>
                    <arguments>install</arguments>
                </configuration>
            </execution>
            <execution>
                <id>npm run build</id>
                <goals>
                    <goal>npm</goal>
                </goals>            <configuration>
                <arguments>run build</arguments>
            </configuration>
            </execution>
        </executions>
    </plugin>
...
</plugins>
  • React build ํŒŒ์ผ ์ด๋™ ์ž๋™ํ™”๋ฅผ ์œ„ํ•œ maven-antrun-plugin ์ถ”๊ฐ€
<plugins>
...
    <plugin>
        <artifactId>maven-antrun-plugin</artifactId>
        <version>3.0.0</version>
        <executions>
            <execution>
                <phase>generate-resources</phase>
                <configuration>
                    <!-- frontend/build ์ดํ•˜ ํŒŒ์ผ์„ Spring boot build ๊ฒฝ๋กœ์˜ classes/static์œผ๋กœ ๋ณต์‚ฌ-->
                    <target>
                        <copy todir="${project.build.directory}/classes/static">
                            <fileset dir="${project.basedir}/frontend/build"/>
                        </copy>
                    </target>
                </configuration>
                <goals>
                    <goal>run</goal>
                </goals>
            </execution>
        </executions>
    </plugin>
    ...
</plugins>

2. build & package

maven clean
maven install
// target ํด๋”์— jar ์ƒ์„ฑ

3. run

$ java -jar target/result.jar

โ—๏ธ ํŽ˜์ด์ง€ ์ด๋™์€ react-router-dom ํ™œ์šฉ

import {
  BrowserRouter as Router,
  Switch,
  Route,
  Link
} from 'react-router-dom';

const App = () => {

  return (
    <div className='App'>
      <Router>
        <div>
          <nav>
            <ul>
              <li>
                <Link to='/'>Home</Link>
              </li>
              <li>
                <Link to='/simulator'>Simulator</Link>
              </li>
              <li>
                <Link to='/users'>Users</Link>
              </li>
            </ul>
          </nav>

          {/* A <Switch> looks through its children <Route>s and
            renders the first one that matches the current URL. */}
          <Switch>
            <Route path='/'>
              <Home />
            </Route>
            <Route path='/simulator'>
              <Simulator />
            </Route>
            <Route path='/users'>
              <Users />
            </Route>
          </Switch>
        </div>
      </Router>
    </div>
  );
};

react-router-dom-result

๐Ÿ“š Reference

Leave a comment