React의 라이프 사이클과 고차 컴포넌트를 이용해서 이미지가 모두 로드된 시점 확인하기
Nextjs API Routes를 통해 API 서버로 파일 보내기
2022-03-29
Explanation
나태해진 나를 위해.. 미루고 미루고 미루던 포스팅하기..
이것도 아주아주 예전, 예전에 회사에서 동생이랑 애먹었던 부분을 포스팅해야지 해야지 하던 걸..
이제서야..
사설이 길었네요…
오늘의 주제는 nextjs로 이루어진 프로젝트에서 이미지를 비롯한 파일 데이터를 API Routes를 통해서 API 서버로 보내는 방법과 그 과정에서 알게 된 것들을 적어보려합니다.
하… 분명히 프로젝트 할때 잘 되었던 것을, 포스팅을 위해 샘플로 다시 만드는데.. 왜 이렇게 안되는 게 많은지..
몇 시간을 애먹었나 모르겠네요..
사실, 블로그를 포스팅하는 일은 뭐랄까.. 약간 복습하는 느낌?? 이라서 아직, 복습보단 예습이 중요할 때가 아닌가 하는 생각에 더 게을렀던 거 같은데.
오늘 한참을 애먹다보니.. 내가 알고 이해했다 생각한 게 사실은 반쪽짜리 였구나 하는 생각이 들었답니다.
앞으로는 작은 부분도 쉽게 넘기지 않고 열심히 공부하며 포스팅해야겠어요..
바로 코드로 확인 할 수 있도록 샘플 코드는 깃에 올려놓았습니다~
https://github.com/falsy/blog-post-example/tree/master/next-js-file-upload
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
// src/pages/index.js import React, { useState, useEffect } from 'react' const Index = () => { const uploadFile = async (file) => { const formData = new FormData() formData.append('file', file) const res = await fetch('/api/upload', { method: 'POST', body: formData }) console.log(res) } const handleChangeFile = (e) => { const [file] = [...e.target.files] uploadFile(file) } return ( <div> <input id="input-file" type="file" accept="image/png, image/jpeg" onChange={handleChangeFile} /> <label htmlFor="input-file">파일 탐색</label> </div> ) } export default Index |
여기까지는 사실 크게 특별한 내용이 없어 보이네요, 사실 얘기하고 싶은 부분이 있긴 한데,
이따가 서버쪽 코드를 이야기하면서 해볼게요.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
// src/pages/api/upload.js import formidable from 'formidable' import FormData from 'form-data' import fs from 'fs' export const config = { api: { bodyParser: false } } export const upload = async (req, res) => { const fileData = await new Promise((resolve, reject) => { const form = new formidable.IncomingForm({ maxFileSize: 5 * 1024 * 1024, keepExtensions: true }) form.parse(req, (err, fields, files) => { if (err) return reject(err) return resolve(files) }) }) const formData = new FormData() const file = fileData.file const readStream = fs.createReadStream(file.filepath) formData.append('file', readStream) const api = await fetch('http://localhost:7777/upload', { method: 'POST', headers: { 'Content-Type': 'multipart/form-data; boundary=' + formData.getBoundary() }, data: formData }) const status = api.status const data = await api.json() console.log(data) if (status === 200) { res.status(status).json({success: true}) } else { return res.status(500).json('Unknown Error') } } export default upload |
사실 여기에서 애먹은 부분이 엄청 많은데요, 처음에 포스팅하려고 생각하고 이야기 하고 싶었던 부분은
1 |
'Content-Type': 'multipart/form-data; boundary=' + formData.getBoundary() |
이 부분 이었어요.
여기서 boundary 값, 이름 그대로 multipart 데이터의 경우에 데이터간 경계를 위한 값으로 사용된답니다.
그 값으로 유니크한 값을 직접 입력해줘도 되지만, 보통은 FormData 객체의 getBoundary 메서드로 사용하는 거 같아요.
여기서 이상한부분?! 똑같이 formData를 보내는데 클라이언트 코드에서는 Content-Type을 선언해주지도 않았고, boundary 값도 없죠?
클라이언트에서는 그 부분을 브라우저가 처리해주는 거 같아요.
1 |
'Content-Type': 'multipart/form-data; boundary=' + formData.getBoundary() |
오히려 클라이언트의 요청에서는 헤더에 위와 같이 입력해주면 오류가 난답니다.
이건 약간 번외인데…
민망하지만, 제가 꽤 오랜 시간을 해맨부분인데..
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import formidable from 'formidable' import FormData from 'form-data' import fs from 'fs' export const config = { api: { bodyParser: false } } ... export default upload |
여기서 저 config 부분이에요, Nextjs 에서는 기본적으로 bodyParser 기능 켜져있어서, 폼 데이터가 Body에 바이너리 형태로 들어가있어요.
그래서 위와 같이 config 설정으로 bodyParser 기능을 꺼줘야, formidable에서 file 정보를 찾을 수 있답니다.
글을 시작할때, 작은 부분도 그냥 넘어가지 않고 열심히 공부해서 포스팅 하겠다고 했지만,
사람이 너무 갑자기 변하면 안되니까…
오늘은 여기까지…
글에 부족한 부분이나 잘못된 부분이 있다면, 알려주시면 정말 감사할 것 같습니다!!