일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- webpack
- TypeScript
- geth
- ts-loader
- 블록체인
- next.js
- 자바스크립트
- Goerlifaucet
- currentTarget
- wallet
- next-connect
- Blockchain
- Sass
- incentive
- methoidID
- set-cookie
- JavaScript
- HTMLFormElement
- S3
- CA불러오기
- Codestates
- 다중서명계약
- 자료구조
- scss
- @debug
- 해쉬테이블
- 코딩테스트
- 스마트컨트랙트
- goerli
- keccak256
- Today
- Total
Minwook’s portfolio
로컬서버 file upload구현 본문
사용스택
next.JS, typeScript, sass, axios, formidable
추가. multer, next-connect
1. 프론트에서 file input으로 data 받기
이미지 구현방식은 useRef로 DOM에 접근해서 이미지파일을 가져오고
onChange를 걸어서 이미지 미리보기가 자동으로 바뀌게 구현할 것 이다.
1) useRef로 DOM에 접근하여 file 데이터 받아오기
const imgRef = useRef<HTMLInputElement>(null);
const imgReset = () => {
if (imgRef.current) {
imgRef.current.value = "";
}
};
return (
...
<form className={img_upload.form}>
<label>file</label>
<input
type="file"
name="cardImg"
id="card-img--input"
ref={imgRef}>
</input>
<button
type="button" //버튼을 누를시 onSubmit()이 실행되지 않도록 button으로 지정
className={img_upload.form_reset}
onClick={imgReset}
>
삭제하기
</button>
</form>
...
)
삭제하기를 누를시에 e.current.value를 삭제시켜서 파일 이름이 뜨지 않도록 한다.
2) onChange를 이용한 미리보기 구현
const ImgUpload = () => {
const imgRef = useRef<HTMLInputElement>(null);
const [imgUrl, setImgUrl] = useState<string>("");
const send = () => {}
const imgReset = () => {
if (imgRef.current) {
imgRef.current.value = "";
//객체 URL 메모리 누수방지
URL.revokeObjectURL(imgUrl);
setImgUrl((_pre) => "");
}
};
return (
<div className={img_upload.container}>
<form className={img_upload.form}>
<label>file</label>
<input
type="file"
name="cardImg"
ref={imgRef}
id="card-img--input"
onChange={(e: React.ChangeEvent<{ files: FileList | null }>) => {
if (e.target.files && e.target.files.length > 0) {
//객체 URL 메모리 누수방지
const file = e.target.files[0];
URL.revokeObjectURL(imgUrl);
//URL생성
setImgUrl((_pre) => URL.createObjectURL(file));
}
}}
></input>
<button
type="button"
className={img_upload.form_reset}
onClick={imgReset}
>
삭제하기
</button>
</form>
//imgUrl이 있을때만 미리보기와 send버튼이 활성화
{imgUrl && (
<>
<div className={img_upload.img_container}>
<Image
className={img_upload.preView}
src={imgUrl}
alt="preview"
width={200}
height={300}
/>
</div>
<button onClick={send} className={img_upload.button}>
submit
</button>
</>
)}
</div>
);
};
- 추가적으로 Blob을 base64로 변환시키는 코드도 구현해보았다 -
const [img, setImg] = useState<Blob | null>(null);
const [imgToBase64, setImgToBase64] = useState<string>("");
const imgRendering = () => {
//window FileReader 사용
const reader = new window.FileReader();
if (img) {
reader.readAsDataURL(img);
reader.onloadend = () => {
const base64 = reader.result;
if (base64) {
//base64를 string으로 변환하여 state 변경
setImgToBase64((_pre) => base64.toString());
}
};
reader.onerror = () => {
alert("upload error!!"); //실패시
};
}
};
useEffect(() => {
//useEffect cleanup 언마운트시 실행
return imgRendering();
}, [img]); //img가 바뀔때만 실행
//미리보기 리셋
const imgReset = () => {
setImg((_pre) => null);
};
return (
...
<form className={img_upload.form}>
<label>file</label>
<input
type="file"
name="cardImg"
id="card-img--input"
onChange={(e: React.ChangeEvent<{ files: FileList | null }>) => {
if (e.target.files && e.target.files.length > 0) {
const file = e.target.files[0];
setImg((_pre) => file);
}
}}
></input>
<button className={img_upload.form_reset} onClick={imgReset}>
삭제하기
</button>
</form>
//imgToBase64가 있을때만 랜더링
{imgToBase64 && (
<>
<div className={img_upload.img_container}>
<Image
className={img_upload.preView}
src={imgToBase64} //base64를 직접 img src로 쓸 수 있다.
alt="preview"
width={200}
height={300}
/>
</div>
<button onClick={send} className={img_upload.button}>
submit
</button>
</>
)}
...
)
2. 프론트에서 서버로 이미지 전송하기 (axios)
...
const imgRef = useRef<HTMLInputElement>(null);
const [imgUrl, setImgUrl] = useState<string>("");
const send = async () => {
if (
imgRef.current &&
imgRef.current.files &&
imgRef.current.files.length > 0
) {
const formData = new FormData();
formData.append("img", imgRef.current.files[0]);
formData.append("title", "title");
const result: AxiosResponse<{ message: string }> = await axios.post(
"/api/upload",
formData,
{
headers: {
"Contest-Type": "multipart/form-data",
},
}
);
console.log(result);
//보내고 나면 리셋
imgReset();
}
};
const imgReset = () => {
if (imgRef.current) {
imgRef.current.value = "";
URL.revokeObjectURL(imgUrl);
setImgUrl((_pre) => "");
}
};
...
3. 서버에서 이미지 받아서 저장하기
node.js에서는 multer를 사용했는데 next에서는 multer를 사용하기 어려웠다.
따라서 formidable를 사용하여 formData를 parse했다.
yarn add formidable @types/formidable
import type { NextApiRequest, NextApiResponse } from "next";
import formidable from "formidable";
import path from "path";
import fs from "fs/promises";
export const config = {
api: {
//next에서는 기본으로 bodyParser가 작동되므로 false로 해준다.
bodyParser: false,
},
};
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
//local에 저장할 path
const imgStoragePath = path.join(
process.cwd() + "/src" + "/public" + "/images"
);
//fs모듈을 사용하여path에 폴더가 없을때엔 생성하도록 할 수 있다.
try {
await fs.readdir(imgStoragePath);
} catch {
await fs.mkdir(imgStoragePath);
}
//추후 s3 버켓으로 보내려고 default는 false로 하였다.
/** true일시 로컬에 저장 */
const readFile = (req: NextApiRequest, saveLocally: boolean = false) => {
const options: formidable.Options = {};
if (saveLocally) {
//true일때 option객체에 path와 filename을 저장
options.uploadDir = imgStoragePath;
options.filename = (name, ext, path, form) => {
return Date.now().toString() + "_" + path.originalFilename;
};
}
return new Promise<{
fields: formidable.Fields;
files: formidable.Files;
}>((resolve, rejects) => {
const form = formidable(options);
form.parse(req, (err, fields, files) => {
if (err) {
rejects(err);
}
console.log(fields);
resolve({ fields, files });
});
});
};
const data = await readFile(req, true);
//files
console.log(data.files.img); //img Blob
//fields
console.log(data.fields.imgToBase64); //img base64
console.log(data.fields.text); //작성한 text를 확인가능
return res.status(201).json({ message: "OK" });
}
이미지를 서버로 보내면 지정된 path에 이미지 파일이 생긴것을 볼 수 있다.
단, 텍스트는 바로 파일로 저장되지않는다.
-- 추가 multer 와 next-connect 를 사용한 middle ware구현 --
nextJS에서 express처럼 미들웨어를 구현하여 file parse를 하려고 공식문서를 터졌지만
nextJS의 내장 미들웨어는 res를 NextRequest 타입으로 받는데 서버 res는 타입을 NextApiRequest로 받아서
서로 타입이 안맞았다. nextJS 내장 미들웨어가 서버 api 미들웨어로는 부적합하다고 생각되어 다른 방식을 찾던중
next-connect라는 서드파티 미들웨어 라이브러리를 찾았다
next-connect를 쉽게 말하자면 nextJS를 express처럼 사용 할 수 있게 해준다
이를 적용한 코드이다.
client
//client axios code
const send = async () => {
if (img) {
const formData = new FormData();
formData.append("imgToBase64", imgToBase64);
formData.append("img", img);
formData.append("title", "title");
const result: AxiosResponse<{ message: string }> = await axios.post(
"/api/hello",
formData,
{
headers: {
"Contest-Type": "multipart/form-data",
},
}
);
console.log(result);
}
};
server
파일위치는 src/api/upload.ts 이다
//api => localhost:3000/api/upload
//upload.ts
import { NextApiRequest, NextApiResponse } from "next";
import nextConnect from "next-connect";
import multer from "multer";
//버퍼 형식으로 메모리에 저장
const upload = multer({ storage: multer.memoryStorage() });
const handler = nextConnect();
interface parsedNextApiRequest extends NextApiRequest {
file?: Express.Multer.File;
files?: Express.Multer.File[];
//이미지 파일은 files에서 확인할 수 있고
//나머지는 body에서 확인가능
body: {
title: string;
imgToBase64: string;
};
}
interface answer {
message: string;
}
//미들웨어사용
handler.use(upload.single("img"));
handler.post((req: parsedNextApiRequest, res: NextApiResponse<answer>) => {
if (req.files) {
//file
console.log(req.file);
//body
console.log(req.body.title);
return res.status(200).json({ message: "OK" });
} else {
return res.status(404).json({ message: "not found" });
}
});
export const config = {
api: {
bodyParser: false, //bodyParser 비활성화
},
};
export default handler;
next-connect를 잘 사용한다면 미들웨어를 통하여 코드 재사용이 편할 것 같다. 공식 문서를 더 읽어볼 필요성을 느꼇다.
next-connect
The method routing and middleware layer for Next.js (and many others). Latest version: 0.13.0, last published: 7 months ago. Start using next-connect in your project by running `npm i next-connect`. There are 35 other projects in the npm registry using nex
www.npmjs.com
참고
with-typescript - CodeSandbox
with-typescript using react-image-lightbox, @rehooks/component-size, react-textfit, formik, buffer-to-data-url, next, react-sticky-mouse-tooltip, pngjs, ethers
codesandbox.io
axios 사용시 폼 데이터 전송하기 (+파일 업로드) | 두글 블로그
axios 의 post 기능은 기본적으로 폼 데이터 전송방식을 사용하지 않기 때문에 서버쪽에서 파라메터를 받는 부분을 수정할 수 없는 상황이라면 문제가 됩니다. 보통 외부 API 서비스를 사용할 때 많
doogle.link
[JS] 📚 Base64 / Blob / ArrayBuffer / File 다루기 총정리
웹 개발을 진행하다 보면 이진 데이터를 다루어야 할 때를 간혹 마주칠 수 있다. 브라우저에선 주로 파일 생성, 업로드, 다운로드 또는 이미지 처리와 관련이 깊고, 서버 사이드인 node.js 에선 파
inpa.tistory.com
[JS] 📚 FormData 사용법 & 응용 총정리 (+ fetch 전송하기)
FormData API 보통 서버에 데이터를 전송하기 위해서는 HTML5 의 폼 태그를 사용해 다음과 같이 메뉴를 구성하여 제출 해본 기억들이 있을 것이다. 아이디 비밀번호 성별 남자 여자 응시분야 영어 수
inpa.tistory.com
image Blob 객체를 url로 바꾸어 img 띄우기, javascript, JavaScript, blob, createObjectUrl, revokeObjectUrl, react, vue,
image Blob 객체를 url로 바꾸어 img 띄우기, javascript, JavaScript, blob, createObjectUrl, revokeObjectUrl, react, vue, window, document
kyounghwan01.github.io
[React/JavaScript] 이미지 파일 업로드 전 미리 보는 방법
이미지 file을 서버에 업로드하기 전, 등록한 파일을 화면에서 미리 보여주고 싶을 때가 있다. URL.createObjectURL() 이용하기 부제: 이미지 미리 보기 url 생성 거두절미하고 리액트 코드부터 보시죠...
xively.tistory.com
'Today I Learned' 카테고리의 다른 글
HTMLFormElement currentTarget 타입지정 (0) | 2023.03.18 |
---|---|
백엔드 s3 upload 구현 (0) | 2023.03.13 |
webpack ts, sass, img loader 설정 (0) | 2023.03.06 |
코딩테스트 문제풀이 소수 찾기 (0) | 2023.01.24 |
코딩테스트 문제풀이 겹치는 선분의 길이 풀이 (0) | 2023.01.10 |