[포스코DX|ReactJS|14주] 58일차 수업, Front-Back API
포스코DX X 비트교육센터 6기 - Front-Back API
예제: Emaillist
Backend API 만들기
- pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- 부모 스타터 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.16</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.poscodx</groupId>
<artifactId>emaillist</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.build.outputEncoding>UTF-8</project.build.outputEncoding>
<java.version>17</java.version>
</properties>
<dependencies>
<!-- Spring Boot Dev Tools: Live Reload -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<!-- Spring Boot Web Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MyBatis Starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- mariadb jdbc driver -->
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
</dependency>
<!-- Spring Boot Test Integration -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<finalName>emaillist</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
<exclude>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
- application.yml
# spring configuration
spring:
application:
name: emaillist
# devtools - live reload
devtools:
livereload:
enabled: true
#mvc
mvc:
static-path-pattern: /assets/**
throw-exception-if-no-handler-found: true
#datasource
datasource:
driver-class-name: org.mariadb.jdbc.Driver
url: jdbc:mariadb://[ip]/webdb?charset=utf8
username: [username]
password: [password]
hikari:
minimum-idle: 10
maximum-pool-size: 20
# mybatis
mybatis:
config-location: classpath:mybatis/configuration.xml
# server
server:
port: 8080
servlet:
context-path: /
encoding:
charset: utf-8
enabled: true
# logging
logging:
pattern:
console: "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{5} - %msg%n"
level:
root: INFO
- EmailistApplication.java : 스프링부트 실행 class
package com.poscodx.emaillist;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class EmaillistApplication {
public static void main(String[] args) {
SpringApplication.run(EmaillistApplication.class, args);
}
}
- configuration.xml : MyBatis 프레임워크의 설정 파일
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<typeAliases>
<typeAlias type="com.poscodx.emaillist.vo.EmaillistVo" alias="emaillistvo" />
</typeAliases>
<mappers>
<mapper resource="mybatis/mappers/emaillist.xml" />
</mappers>
</configuration>
- emaillist.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="emaillist">
<select id="findAll" resultType="emaillistvo">
<![CDATA[
select no, first_name, last_name, email
from emaillist
order by no desc
]]>
</select>
<insert id="insert" parameterType="emaillistvo">
<![CDATA[
insert
into emaillist
values (null, #{firstName }, #{lastName }, #{email })
]]>
</insert>
</mapper>
- JsonResult.java : JSON 형식의 응답을 생성하는 데 사용
package com.poscodx.emaillist.dto;
public class JsonResult {
private String result; //"success" or "fail"
private Object data; // if success, set!
private String message; // if fail, set!
private JsonResult(Object data) {
this.result = "success";
this.data = data;
}
private JsonResult(String message) {
this.result = "fail";
this.message = message;
}
public String getResult() {
return result;
}
public Object getData() {
return data;
}
public String getMessage() {
return message;
}
public static JsonResult success(Object data) {
return new JsonResult(data);
}
public static JsonResult fail(String message) {
return new JsonResult(message);
}
}
- GlobalExceptionHandler.java : 전역 예외 처리 클래스로 클라이언트에게 이해하기 쉬운 오류 메시지를 제공한다.
package com.poscodx.emaillist.exception;
import java.io.PrintWriter;
import java.io.StringWriter;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.NoHandlerFoundException;
import com.poscodx.emaillist.dto.JsonResult;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {
@ResponseBody
@ExceptionHandler(Exception.class)
public ResponseEntity<JsonResult> handlerException(Exception e) {
// 로깅(Logging)
StringWriter errors = new StringWriter();
e.printStackTrace(new PrintWriter(errors));
log.error(errors.toString());
// 응답
JsonResult jsonResult =
(e instanceof NoHandlerFoundException) ?
JsonResult.fail("Unknown Request") :
JsonResult.fail(errors.toString());
return ResponseEntity
.status(HttpStatus.OK)
.body(jsonResult);
}
}
- EmaillistVo.java : 룸북을 적용한 경우이다.
package com.poscodx.emaillist.vo;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Setter
@Getter
@ToString
public class EmaillistVo {
private Long no;
private String firstName;
private String lastName;
private String email;
}
- APIControllor.java
package com.poscodx.emaillist.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import com.poscodx.emaillist.dto.JsonResult;
import com.poscodx.emaillist.repository.EmaillistRepository;
@RestController
public class ApiController {
@Autowired
private EmaillistRepository emaillistRepository;
@GetMapping("/api")
public ResponseEntity<JsonResult> read() {
return ResponseEntity
.status(HttpStatus.OK)
.body(JsonResult.success(emaillistRepository.findAll()));
}
}
- EmaillistRepository.java
package com.poscodx.emaillist.repository;
import java.util.List;
import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import com.poscodx.emaillist.vo.EmaillistVo;
@Repository
public class EmaillistRepository {
@Autowired
private SqlSession sqlSession;
public List<EmaillistVo> findAll() {
return sqlSession.selectList("emaillist.findAll");
}
}
Frontend
- App.js
import React, { useState, useEffect } from "react";
import "./assets/scss/App.scss";
import RegisterForm from "./RegisterForm";
import SearchBar from "./SearchBar";
import Emaillist from "./Emaillist";
// import data from './assets/json/data';
function App() {
// emaillist 저장 상태 변수
// 초기값 null 설정
const [emails, setEmails] = useState(null);
// email을 keyword를 통해 찾아내는 함수
const searchEmail = (keyword) => {
const newEmails = data.filter(
(email) =>
email.firstName.indexOf(keyword) !== -1 ||
email.lastName.indexOf(keyword) !== -1 ||
email.email.indexOf(keyword) !== -1
);
setEmails(newEmails);
};
// post를 통해 서버로 전송하는 함수.
// fetch를 사용하여 서버의 /api 엔드포인트로 POST 요청
const addEmail = async (email) => {
fetch("/api", {
method: "post",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(email),
});
};
// 서버로부터 이메일 목록을 가져오는 함수
// fetch를 사용하여 서버의 /api 엔드포인트로 GET 요청
const fetchList = async () => {
try {
const response = await fetch("/api", {
method: "get",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: null,
});
if (!response.ok) {
throw new Error(`${response.status} ${response.statusText}`);
}
const json = await response.json();
if (json.result !== "success") {
throw new Error(`${json.result} ${json.message}`);
}
console.log(json.data);
// 요청이 성공하면 emails 상태 변수를 업데이트
setEmails(json.data);
} catch (err) {
console.error(err);
}
};
// useEffect 훅을 사용하여 애플리케이션이 처음 렌더링될 때 fetchList 함수를 호출
// 빈 배열의 효과로 한 번만 실행
useEffect(() => {
fetchList();
}, []);
return (
<div id={"App"}>
<RegisterForm addEmail={addEmail} />
<SearchBar searchEmail={searchEmail} />
{emails === null ? null : <Emaillist emails={emails} />}
</div>
);
}
export { App };