Summernote, Node.js Express 환경에서 AWS S3 이미지 클라우드 연동

HK_Jang·2022년 3월 12일
0

0. 간단한 텍스트 에디터를 찾고 있는가?

블로그 프로젝트를 진행하던 중, 포스팅을 위해서 Textarea만으로는 부족할거야...를 일찍이 깨닫고 Markdown Editor 혹은 위지윅 에디터를 알아봤다.

하지만 나는 생활코딩 백엔드만 수강한 늅늅이 웹 개발자... React나 Vue같은 화려한(?) 프론트엔드 개발은 할 줄 모른다. 간단한 HTML과 JS 지식밖에 없는걸..

당신도 같은 고민을 하고 있으면 Summernote가 제격이다!

Summernote의 홈페이지. 나도 놀러가고 싶다..
https://summernote.org/

Summernote는 기본적으로 위지윅 (WYSIWYG) 에디터다. 뭔가 말이 어려워서 거창해 보이지만, 우리가 흔히 네이버 블로그, 카페 등에서 보는 텍스트 에디터가 위지윅 에디터다. 그럼 Summernote의 장/단점은 뭐가 있을까?

1. Summernote의 장단점

  • 장점

    1. 적용의 간편성

    솔직히 대부분의 요즘 라이브러리들이 그렇듯이, CDN (사실상 링크 복사-붙여넣기) 와 간단한 JQuery 지식만 있으면 된다.
    필자는 JQuery가 튀어나와서 지레 겁먹었는데, Vanilla JS를 착실히 익혀온 당신, 겁낼 필요 없다. 별로 어렵지 않으니까!

    2. FE 지식을 요구하지 않음

    JQuery와 디자인적인 요소로는 Bootstrap만을 사용하는 단순한 구조이기 때문에, 개조(?)를 위해서 크게 요구하는게 없다. 필자는 원래 Toast UI를 적용하고 싶었는데... 결국 간단한 개조를 하려고 해도 React지식을 요구해 포기하게 됐다. 그만큼 간편하다!
  • 단점

    1. 낡아가는 라이브러리

    JQuery 자체가 정말 간단하다고는 해도, 결국 낡은 기술이다. 대부분의 라이브러리들이 Vanilla JS가 점점 완성도가 높아지면서 JQuery를 제외하고 있다.
    심지어 Summernote는 디자인을 위해 일부 Bootstrap에 의존하고 있는데, 그 Bootstrap조차도 5버전으로 넘어오면서 JQuery를 제외시켰다. 그래서 Bootstrap을 로드하지 않아도 되는 Lite버전을 사용하는게 아니라면, Bootstrap 4 혹은 3버전을 로드시켜줘야 한다.

    2. 뭔가 부족한 기능

    이거는 솔직히 설명하기는 어려우나.. 뭔가 기능이 한개씩 없는 느낌이다. 예를 들면, 필자는 글 중간중간 image upload를 위해 클라우드를 활용하려 했는데... 이미지를 올릴 때 제어할 수 있는 JQuery함수는 존재했으나 만약 이미지를 백스페이스나 Delete키를 이용해서 지울 때는 제어할 수 있는 방법이 없다는지... 여하튼 좀 뭔가 부족하다 ㅠ.ㅠ 무료니까 어쩔 수 없지..

2. 적용 방법

https://summernote.org/getting-started/ 이 곳을 참고하도록 하자. CDN을 적용시켜 간단히 적용이 가능하다.

3. 이미지 업로드 AWS S3 연동

  1. 개요
    summernote로 작성하는 글의 본문에 이미지를 넣으려면, insertImage 아이콘을 눌러 적용하면 된다. 다만 기본 설정대로 이용하면 이미지를 Base64로 바꾼 값을 저장해야 하는데, 이게 용량이 꽤 크기 때문에 DB에 부담이 된다. 따라서 클라우드에 저장 후 URL을 받아 본문에 img태그를 삽입한 형태로 저장하는 방법이 바람직하다.

  2. multer, multer-s3
    multer와 multer-s3 패키지를 통해 파일 업로드 방법을 구성해야 한다. 해당 내용은 너무나 관련 포스팅이 많기 때문에... 정말 좋은 예시 링크를 첨부하도록 하겠다. https://velog.io/@neity16/NodeJS-S3-%EC%97%B0%EA%B2%B0%ED%95%98%EA%B8%B0-multer

  1. routes
    routes에는 3가지 경로가 필요하다.
    1. summernote editor가 나타날 경로
    2. file(image) upload를 처리해줄 경로
    3. 글을 처리해줄 경로

router.js

const { postWriteView, insertPost, insertImage } = requires('./postController');
const upload = require('./multer');
router.get('/write', postWriteView); // summernote editor
router.post('/insertImage', upload.single('img'), insertImage); // file upload 처리
router.post('/insertPost', insertPost)  // 글을 처리해줄 경로
module.exports = router;

main.js

...
app.use('view engine, 'ejs');
app.use('/', require('./router'));

const PORT = process.env.PORT || 3000;
app.listen(PORT, console.log("Server start  at port : "+PORT));
...
  1. Controller
    postController.js
 const postWriteView = (req, res) => {
    res.render("postWrite", {});
}
const insertImage = async(req, res) => {
    imgurl = '';
    if(req.file !== undefined) {
        var imgurl = req.file.location; // router에서 붙인 multer가 반환한 url (aws s3 object url)
    }
    res.json(imgurl); // json 형태로 반환해주어야 View에서 처리가 가능하다.
}
const insertPost = (req, res) => {
	console.log(req.body);
};
  1. postWrite.ejs -- 중요!
 <!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>summernote</title>
    <!-- CDN 추가 시작 -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous">
    <script
    src="https://code.jquery.com/jquery-3.5.1.min.js"
    integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0="
    crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-fQybjgWLrvvRgtW6bFlB7jaZrFsaBXjsOMm/tB9LTS58ONXgqbR9W8oWht/amnpF" crossorigin="anonymous"></script>
    <link href="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote.min.css" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote.min.js"></script>
    <!-- CDN 추가 부분 끝 -->
</head>
<body>
<form method="post" action="/insertPost">
    <textarea id="summernote" name="editordata"></textarea>
    <button type="submit">등록</button>
</form>
    
</body>
<script>
// summernote init
$(function(){
  $('#summernote').summernote({ 
    placeholder: '내용',
    tabsize: 2,
    focus: true,
    callbacks:{
      // onImageUpload를 통해 이미지 업로드시 동작 개조 가능
      // 개조하지 않으면 Base64로 이미지가 전환돼서 img태그로 바뀐뒤 본문에 추가된다.
      onImageUpload: function(files){
        sendFile(files[0], this);
      }
    }
  });
});

function sendFile(file, editor){
  data = new FormData()
  data.append("img", file)
  // id 'img'로 file form 데이터 추가
  $.ajax({
    data: data,
    type: "POST",
    // 이미지 처리를 할 url
    url: "/insertImage",
    cache: false,
    contentType: false,
    // multer-s3를 활용하므로 multipart/form-data형태로 넘겨줘야 한다.
    enctype: "multipart/form-data",
    processData: false,
    success: function (response) {
      var imgurl = $('<img>').attr({
        'src': response,
        // json형태로 반환되는 주소.
        'crossorigin': 'anonymous',
        // crossorigin attr을 삽입하지 않으면 CORS에러가 난다!
    });
      $("#summernote").summernote("insertNode", imgurl[0]);
      // insertNode는 html tag를 summernote 내부에 삽입해주는 기능.
    },
  })
}
</script>
</html>


summernote에 이미지가 삽입된 모습.

mysql DB에 저장된 모습이다. 필자의 DB 결과의 세 번째 항목이 위 코드상으론 콘솔에 찍힐 것이다.

profile
살아남는 종은 강한 종이나 똑똑한 종이 아닌, 변화에 적응하는 종이다. - 찰스 다윈

0개의 댓글