[Next.js, Java] API 비동기 통신 방법 (fetch / FormData / JSON 응답 정리)

정석환·2025년 4월 23일

api 비동기 통신

1. 기본 형태

export const postRequest = async (formData) => {
    const response = await fetch("백엔드 컨트롤러 주소",{
        method: "POST", // HTTP POST 방식으로 요청
        body: formData,	// formData 객체를 요청 본문으로 전달
    })

    if (!response.ok) {
        throw new Error("post요청 실패")
    }

    return await response.json() // 응답 데이터를 JSON 형태로 반환
}

여기서 response.json()은 백엔드에서 작성된 DTO 객체를 JSON으로 직렬화한 결과를 받아오는 것이다.

2. next.js 서비스 예시

export const createTradePost = async (formData) => {
    const response = await fetch("http://localhost:8080/api/Trade/createPost",{
        method: "POST",
        body: formData,
    })

    if (!response.ok) {
        throw new Error("게시글 작성 실패")
    }

    return await response.json()
}

백엔드가 ResponseEntity<TradePostMessageResponse>로 반환하면, 그 구조가 프론트에서 그대로 JSON으로 들어온다.
예시로 필자가 직접 적용한 부분을 보자

3. Java Controller 예시

    @PostMapping(path = "/createPost", consumes = "multipart/form-data")
    public ResponseEntity<TradePostMessageResponse> createTradePost(@RequestPart("data") CreateTradeBoardRequest request, @RequestPart("images") List<MultipartFile> images) throws IOException {

        TradePostMessageResponse result = tradePostService.createTradePost(request, images);

        return ResponseEntity.ok(result);

    }
  • @RequestPart("data")는 FormData의 data 키에 해당하는 JSON 문자열을 DTO로 변환해서 받는 어노테이션이다.
  • @RequestPart("images")는 FormData로 업로드된 파일 리스트를 MultipartFile로 매핑한다.
    위의 코드에서 result부분을 ResposeEntity의 ok에 넣어서 보내준다.
    그렇다면 이 result는 뭐가 들어있을까?

. Java Service 예시

    @Override
    public TradePostMessageResponse createTradePost(CreateTradeBoardRequest request, List<MultipartFile> sourceImage) throws IOException {

        TradePost tradePost = saveTradePost(request);

        log.info("tradePost = {}", tradePost);

        // 컨버트한 바이트 배열 리스트.
        List<byte[]> files = productImageConvertService.convert(sourceImage);


        for (byte[] fileData : files) {

            //유니크 파일 경로 만들기
            String fileName = UUID.randomUUID().toString();

            //s3 업로드
            String imageUrl = s3StorageService.upload(fileData,fileName);
            System.out.println(imageUrl);

            // product image 객체 생성
            ProductImage productImage = productImageService.createTradeProductImage(tradePost.getPostId(), imageUrl);

        }

        return TradePostMessageResponse.builder()
                .message("Success Created Trade Post")
                .result(true)
                .build();


    }

중간의 코드는 신경 안써도 된다.
그냥 return 부분만 보면 되는데
Java에서 TradePostMessageResponse는 단순한 DTO이지만,
Spring Boot에서는 이를 자동으로 JSON으로 직렬화하여 응답 본문으로 전송한다.
그러면 이러한 결과물이 json으로 어떻게 들어갈까?

4. Next.js 반환 되는 값

{
  "message": "Success Created Trade Post",
  "result": true
}

이러한 값으로 반환이 된다.

5.참고용 api

export const createTradePost = async (formData) => {
    const response = await fetch("http://localhost:8080/api/Trade/createPost",{
        method: "POST",
        body: formData,
    })

    console.log("fetch",response)

    if (!response.ok) {
        throw new Error("게시글 작성 실패")
    }

    return response.json()
}

export const getTop10Post = async () => {
    const response = await fetch("http://localhost:8080/api/Trade/Top10Post",{
        method: "GET",
        headers: {
            "Content-Type": "application/json",
        },
    })

    console.log("fetch",response)

    return response.json()
}

export const getAllPost = async (page, sort) => {
    const response = await fetch(`http://localhost:8080/api/Trade/readAllPost?page=${page}&sort=${sort}`, {
        method: "GET",
        headers: {
            "Content-Type": "application/json",
        },
    });

    console.log("📡 응답 상태코드:", response.status);

    if (!response.ok) {
        throw new Error("모든 게시글 조회 실패");
    }

    return response.json();
};

export const getPostInfoAndImages = async (postId) => {
    const response = await fetch(`http://localhost:8080/api/Trade/readPost/${postId}`, {
        method: "GET",
        headers: {
            "Content-Type": "application/json",
        },
    });

    if (!response.ok) {
        throw new Error("게시글 조회 실패");
    }

    return response.json();
};

export const bumpPost = async (postId) => {
    const response = await fetch(`http://localhost:8080/api/Trade/bumpPost/${postId}`, {
        method: "PUT" ,
        headers: {
            "Content-Type": "application/json",
        },
    });

    if (!response.ok) {
        throw new Error("끌어올리기 실패");
    }

    return response.json();

}

export const deletePost = async (postId) => {
    const response = await fetch(`http://localhost:8080/api/Trade/deletePost/${postId}`, {
        method: "DELETE",
        headers: {
            "Content-Type": "application/json"
        },
    });

    if (!response.ok) {
        throw new Error("게시글 삭제 실패");
    }

    return response.json();
}

export const updateOnlyStatusTradePost = async (postId, status) => {
    const response = await fetch(`http://localhost:8080/api/Trade/updatePostStatus/${postId}`, {
        method: "PUT",
        headers: {
            "Content-Type": "application/json",
        },
        body: JSON.stringify({ status })
    })

    if (!response.ok) {
        throw new Error("게시글 상태 변경 실패");
    }

    return response.json();
}

export const updateTradePost = async (postId, formData) => {

    const response = await fetch(`http://localhost:8080/api/Trade/updatePost/${postId}`, {
        method: "PUT",

        body: formData 
    });

    if (!response.ok) {
        throw new Error(`수정 실패: ${response.status}`);
    }

    return response.json();
};

반환되는 JSON에는 단순한 메시지뿐만 아니라 원하는 DTO 객체를 포함할 수 있다.
또한 JSON 형식으로 데이터를 주고받고 싶다면, headers에 아래와 같이 명시해주는 것이 좋다

headers: {
  "Content-Type": "application/json
}
profile
자바,스프링 백엔드 개발자를 꿈꾸는 초보아빠

0개의 댓글