위지윅 에디터인 CK Editor를 구현하는 도중 대표님이 '이미지 URL로 변환하여 사용할 것이다.' 라고 말씀하셔서 그렇게 만들어보려고 했다.
문제는 테스트 서버가 없었고, 이 사진이 URL로 잘 변환하여 들어갈 수 있는지, 작동하는지도 모르는 것이었다. 그 과정을 밟아보자.
먼저 왜 이미지 업로드 서버가 필요한지 Araboza.
아래 코드는 CK Editor의 플러그인, 툴바 설정 중 Simple upload adaptor다.
...
language: 'ko',
table: {
contentToolbar: ['tableColumn', 'tableRow', 'mergeTableCells'],
},
simpleUpload: {
// 이미지가 업로드되는 URL
uploadUrl: 'http://localhost:8080/upload/image',
// XMLHttpRequest의 withCredentials 속성을 활성화하기
withCredentials: true,
// 업로드 서버로 전송되는 XMLHttpRequest 헤더
// headers: {
// 'X-CSRF-TOKEN': 'CSRF-Token',
// Authorization: 'Bearer <JSON Web Token>',
// },
},
};
CK Editor는 Base64 변환도 가능하고, 당연히 URL을 가져와 이미지로 보여주는 기능도 가능하다. 여기서 대표님은 URL을 가져와 이미지를 보여주는 방식으로 채택했고, Base 64는 편하지만 성능이 그다지 좋지 않기 때문에 나도 동의하는 바였다.
그래서 URL로 하는 방식을 택했고, 이미지를 업로드 하면 URL로 변환되는 서버, 로직이 필요했던 것이다.
그런데 프론트에서는 그런 기능을 지원하지 못하기 때문에 오랜만에 녹슨 칼을 꺼내들었다. 바로 Node.JS로 서버를 만드는 것이다.
내 1시간 동안의 고민이 담겨 있는 커밋이다.. Node.JS가 오랜만이었기에 시작했다.
먼저 루트에 server/image-upload-express.js
를 만들었다. 왜 TS로 안했냐면 아직 Node.JS에선 그다지 안정적이지 못하다고 들어서 간단하게 JS로 만들었다.
거기에 package.json
에 구동 명령어인 "server": "node server/image-upload-express.js"
만 넣어주면 구동 준비는 끝났다.
먼저 Node.JS 프레임워크인 express
와 expess-fileupload
를 깔고, 파일명 생성 규칙으로 쓸 uuid
도 깔자. 내장 기능으로만 하려고 했는데 안되더라.. 내장 기능으로만 하신 분이 있다면 댓글로 적어주시길 간곡히 빈다.
그리고 express
를 사용하여(use
) 로직을 짜보자.
const express = require('express');
const fileUpload = require('express-fileupload');
const path = require('path');
const { v4: uuidv4 } = require('uuid');
const app = express();
const uploadDir = path.join(__dirname, 'static/upload/images');
// CORS 설정
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', 'http://localhost:3000');
res.header(
'Access-Control-Allow-Headers',
'Origin, X-Requested-With, Content-Type, Accept'
);
res.header('Access-Control-Allow-Credentials', 'true');
next();
});
// 파일 업로드를 위한 미들웨어 설정
app.use(fileUpload());
app.post('/upload/image', (req, res) => {
if (!req.files || !req.files.upload) {
return res.status(400).json({ error: 'No file uploaded' });
}
const uploadFile = req.files.upload;
const uploadId = `${uuidv4()}.${path.extname(uploadFile.name)}`;
uploadFile.mv(path.join(uploadDir, uploadId), (err) => {
if (err) {
console.error('Error saving the file:', err);
return res.status(500).json({ error: 'Internal Server Error' });
}
const response = {
url: `/static/upload/images/${uploadId}`,
...req.body,
};
return res.json(response);
});
});
const port = 8080;
app.listen(port, () => {
console.log(`Server is listening on port ${port}`);
});
fileUpload()
미들웨어 설정post
메소드 설정근데 안됐다.
Error saving the file: [Error: ENOENT: no such file or directory, open '/Users/yoontaeyeon/Desktop/Programming/MofS/web/2023/web-23-hound/server/static/upload/images/e9292409-c2b2-4279-8e28-728e6213b939..png'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/yoontaeyeon/Desktop/Programming/MofS/web/2023/web-23-hound/server/static/upload/images/e9292409-c2b2-4279-8e28-728e6213b939..png'
}
이런 에러가 떴다. 어따 저장할지 몰라서 에러가 나온 것이다.. 그냥 자동으로 해주면 어디 겁나나?
그래서
...
const fs = require('fs');
const { v4: uuidv4 } = require('uuid');
...
// 디렉토리가 존재하지 않으면 생성
fs.mkdirSync(uploadDir, { recursive: true });
app.post('/upload/image', (req, res) => {
...
이렇게 해줬다. uploadDir
변수의 위에 fs
모듈을 가져오고, uploadDir
파일 생성 코드를 추가했다.
근데 또 안됐다.
사진을 이제 요청하면 서버에 파일 생성이 잘 됐다! 근데 에디터에서의 사진이 깨졌다. 왜냐하면
<figure class="image"><img src="/static/upload/images/eceece0f-e934-448b-8a62-42131484b866..jpeg"></figure>
이딴 식으로 갔기 때문에 응답데이터를 수정해야했다.
...
const uploadId = `${uuidv4()}.${path.extname(uploadFile.name)}`;
uploadFile.mv(path.join(uploadDir, uploadId), (err) => {
if (err) {
console.error('Error saving the file:', err);
return res.status(500).json({ error: 'Internal Server Error' });
}
const response = {
url: `/static/upload/images/${uploadId}`,
...req.body,
};
return res.json({ url: `http://localhost:8080${response.url}` });
});
});
...
이런 식으로 하면 응답으로 http://localhost:8080/~~~
이렇게 나올 것이다.
응답은 URL 형식으로 잘 갔다. 근데 또 깨져서 나온다 ㅋㅋ 왜그럴까?
바로 static
폴더 접근 권한을 줘야하는데 그 로직을 안짜서 이미지 로딩이 되지 않았다. 바로 static
폴더 접근 권한을 줘보자.
...
// 파일 업로드를 위한 미들웨어 설정
app.use(fileUpload());
// 정적 파일 제공을 위한 미들웨어 설정
app.use('/static', express.static(path.join(__dirname, 'static')));
// 디렉토리가 존재하지 않으면 생성
fs.mkdirSync(uploadDir, { recursive: true });
...
짠! Express 서버에서 정적 파일을 제공하기 위해 /static
도메인에 express.static
미들웨어를 설정하는 코드를 작성했다.
즉, /static
경로로 들어오는 요청은 static
디렉토리의 파일을 찾아 클라이언트에게 제공하도록 변경했다.
이렇게 하니 진짜 됐다! 결과를 보자.
에디터에 이미지도 잘 들어왔고, URL을 보니 /static
에 잘들어왔고, 파일도 아마 들어있을 테니 렌더링 되는 것이다.
이렇게 해서 간단한(?) 이미지 업로더 서버를 만들어 봤다. 전체 소스가 필요하다면 아래 코드를 그냥 복붙해서 잘 사용해봐라. 나같이 시간 버리지말고,, 뿅이다!
const express = require('express');
const fileUpload = require('express-fileupload');
const path = require('path');
const fs = require('fs');
const { v4: uuidv4 } = require('uuid');
const app = express();
const uploadDir = path.join(__dirname, 'static/upload/images');
// CORS 설정
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', 'http://localhost:3000');
res.header(
'Access-Control-Allow-Headers',
'Origin, X-Requested-With, Content-Type, Accept'
);
res.header('Access-Control-Allow-Credentials', 'true');
next();
});
// 파일 업로드를 위한 미들웨어 설정
app.use(fileUpload());
// 정적 파일 제공을 위한 미들웨어 설정
app.use('/static', express.static(path.join(__dirname, 'static')));
// 디렉토리가 존재하지 않으면 생성
fs.mkdirSync(uploadDir, { recursive: true });
app.post('/upload/image', (req, res) => {
if (!req.files || !req.files.upload) {
return res.status(400).json({ error: 'No file uploaded' });
}
const uploadFile = req.files.upload;
const uploadId = `${uuidv4()}.${path.extname(uploadFile.name)}`;
uploadFile.mv(path.join(uploadDir, uploadId), (err) => {
if (err) {
console.error('Error saving the file:', err);
return res.status(500).json({ error: 'Internal Server Error' });
}
const response = {
url: `/static/upload/images/${uploadId}`,
...req.body,
};
return res.json({ url: `http://localhost:8080${response.url}` });
});
});
const port = 8080;
app.listen(port, () => {
console.log(`Server is listening on port ${port}`);
});