25.블로그 포스트 작성 및 에디터 기능 추가, Next.js에 마크다운 에디터 추가하기

최근 몇 년간 블로그와 콘텐츠 관리 시스템에서 마크다운(Markdown)의 사용이 급증했습니다.
마크다운은 간편하고 직관적인 문법을 제공하여, 사용자가 텍스트를 쉽게 포맷할 수 있게 해줍니다.
이번 포스트에서는 Next.js를 기반으로 마크다운 에디터를 추가하는 방법에 대해 자세히 설명하겠습니다.
또한 블로그 포스트 작성 및 에디터 기능 추가에 필요한 기술 스택과 구성 요소에 대해서도 다룰 것입니다.

1. Next.js 개요

Next.js는 React 기반의 정적 웹사이트 및 서버 사이드 렌더링(SSR) 웹 애플리케이션을 구축하기 위한 프레임워크입니다.
Next.js는 SEO 친화적이며, 페이지 최적화 및 코드 스플리팅을 자동으로 처리하여 성능을 극대화합니다.
이외에도 API 라우팅, 이미지 최적화, 정적 생성 등 다양한 기능을 제공하여 개발자들이 생산성을 높일 수 있도록 돕습니다.

Next.js 설치 및 설정

Next.js 프로젝트를 시작하려면 Node.js 및 npm이 설치되어 있어야 합니다.
다음 명령어를 통해 Next.js 프로젝트를 생성할 수 있습니다.

npx create-next-app my-blog

2. 마크다운 및 마크다운 에디터

마크다운은 텍스트 기반의 경량 마크업 언어로, HTML과 유사한 형식을 사용하여 문서의 구조를 정의할 수 있습니다.
마크다운을 활용하면 블로그 글 작성 시 포맷팅이 간편하고 빠르게 진행될 수 있습니다.
이번 섹션에서는 간단한 마크다운 문서를 작성해 보겠습니다.

마크다운 예시


# 제목 1
## 제목 2
### 제목 3

이것은 두 번째 단락입니다.

- 리스트 항목 1
- 리스트 항목 2
- 리스트 항목 3

[링크 텍스트](http://example.com)

**강조된 텍스트**
    

3. Next.js에 마크다운 에디터 추가하기

Next.js 프로젝트에 마크다운 에디터를 추가하기 위해 여러 패키지를 설치하고 설정해야 합니다.
다음은 우리가 사용할 주요 패키지 목록입니다.

  • react-markdown: 마크다운을 React 컴포넌트로 변환합니다.
  • react-quill: WYSIWYG 마크다운 에디터를 제공합니다.
  • remark-gfm: GitHub 스타일 마크다운을 지원합니다.

패키지 설치

아래 명령어를 사용하여 필요한 패키지를 설치합니다.

npm install react-markdown react-quill remark-gfm

에디터 컴포넌트 만들기

React 컴포넌트를 사용하여 마크다운 에디터를 만들고, 이를 Next.js에 통합합니다.
components/MarkdownEditor.js라는 파일을 생성하고 다음 코드를 추가합니다.


import React, { useState } from 'react';
import ReactQuill from 'react-quill';
import 'react-quill/dist/quill.snow.css'; // 스타일 추가

const MarkdownEditor = () => {
    const [text, setText] = useState('');

    return (
        

미리보기

); }; export default MarkdownEditor;

페이지에서 에디터 사용하기

이제 작성한 에디터 컴포넌트를 페이지에서 사용해 보겠습니다.
pages/index.js 파일에 아래의 코드를 추가합니다.


import MarkdownEditor from '../components/MarkdownEditor';

export default function Home() {
    return (
        

블로그 포스트 작성

); }

4. 마크다운 파일 저장 및 불러오기

작성한 내용은 어딘가에 저장되어야 합니다. 이번 섹션에서는 마크다운 파일을 저장하고 불러오는 로직을 구현합니다.
파일 시스템 모듈을 사용하여 만들 수 있습니다.

파일 시스템 사용하기


import fs from 'fs';
import path from 'path';

const filePath = path.join(process.cwd(), 'posts', 'my-post.md');

const savePost = (content) => {
    fs.writeFileSync(filePath, content);
};

const loadPost = () => {
    return fs.readFileSync(filePath, 'utf8');
};
    

요청 핸들러 구축하기

Next.js의 API 라우트를 사용하여 클라이언트와 서버 간의 데이터 요청을 처리할 수 있습니다.
pages/api/posts.js 파일을 생성하고 POST, GET 요청을 처리하는 코드를 추가합니다.


export default function handler(req, res) {
    if (req.method === 'POST') {
        savePost(req.body.content);
        res.status(200).json({ message: '포스트 저장 완료' });
    } else {
        const content = loadPost();
        res.status(200).json({ content });
    }
}
    

5. 전체 구현 예시

위의 모든 코드를 모아 완성된 블로그 포스트 작성기 에디터를 구현해보겠습니다. 최종 코드는 아래와 같습니다.


// components/MarkdownEditor.js
import React, { useState, useEffect } from 'react';
import ReactQuill from 'react-quill';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import 'react-quill/dist/quill.snow.css';

const MarkdownEditor = () => {
    const [text, setText] = useState('');

    useEffect(() => {
        const loadPost = async () => {
            const response = await fetch('/api/posts');
            const data = await response.json();
            setText(data.content);
        };

        loadPost();
    }, []);

    const savePost = async () => {
        await fetch('/api/posts', {
            method: 'POST',
            body: JSON.stringify({ content: text }),
            headers: {
                'Content-Type': 'application/json',
            },
        });
    };

    return (
        

미리보기

); }; export default MarkdownEditor; // pages/index.js import MarkdownEditor from '../components/MarkdownEditor'; export default function Home() { return (

블로그 포스트 작성

); } // pages/api/posts.js import fs from 'fs'; import path from 'path'; const filePath = path.join(process.cwd(), 'posts', 'my-post.md'); const savePost = (content) => { fs.writeFileSync(filePath, content); }; const loadPost = () => { return fs.readFileSync(filePath, 'utf8'); }; export default function handler(req, res) { if (req.method === 'POST') { savePost(req.body.content); res.status(200).json({ message: '포스트 저장 완료' }); } else { const content = loadPost(); res.status(200).json({ content }); } }

6. 마무리

이번 포스트에서는 Next.js를 기반으로 마크다운 에디터를 추가하는 방법에 대해 알아보았습니다.
간단한 WYSIWYG 에디터를 통해 블로그 포스트를 작성하고, 서버에 데이터를 저장하는 방법까지 다뤘습니다.
앞으로 추가적인 기능을 구현하여 더욱더 풍부한 블로그 작성 경험을 제공할 수 있습니다.
Next.js의 애드온을 사용하여 자신만의 블로그 플랫폼을 만들어 보세요!

28.이미지 업로드 및 미디어 관리, Next.js와 Cloudinary, AWS S3 등을 이용한 이미지 업로드

현대 웹 애플리케이션에서 이미지 및 미디어는 필수적인 요소입니다. 사용자 경험을 높이고, 시각적인 요소를 강화하며, 브랜딩을 강화하는 데 중요한 역할을 합니다. 이미지 업로드와 관련된 기능을 구현하는 것은 많은 개발자들에게 도전 과제가 될 수 있습니다. 이번 포스팅에서는 Next.js와 함께 Cloudinary, AWS S3를 활용하여 이미지 업로드 및 미디어 관리하는 방법에 대해 자세히 다루어 보겠습니다.

1. 이미지 업로드의 중요성

이미지는 웹 페이지의 디자인에 큰 영향을 미치며, 콘텐츠를 시각적으로 풍부하게 만드는 데 기여합니다. 적절하게 관리되지 않은 이미지는 성능 저하, 느린 로딩 속도, 불필요한 서버 사용 등을 초래할 수 있습니다. 따라서 효율적인 이미지 업로드 및 관리는 매우 중요합니다.

2. Next.js 소개

Next.js는 React 기반의 서버 사이드 렌더링(SSR) 및 정적 웹 애플리케이션을 만들기 위한 프레임워크입니다. Next.js는 SEO 최적화, 빠른 페이지 로딩速度, 파일 기반 라우팅 등 다양한 장점을 제공합니다. 이러한 특성 덕분에 Next.js는 이미지 및 미디어 관리를 포함하는 복잡한 기능들을 쉽게 구현하는 데 유리한 도구입니다.

3. Cloudinary 및 AWS S3 소개

3.1 Cloudinary

Cloudinary는 클라우드 기반의 미디어 관리 플랫폼으로, 이미지 및 비디오의 업로드, 저장, 변환 및 처리 기능을 제공합니다. 개발자는 손쉽게 изображения тәжірибесін 개선할 수 있습니다. 특히 Cloudinary는 이미지 최적화, 자동 형식 변환 및 다양한 효과를 지원하여 빠르고 일관된 미디어 처리 경험을 제공합니다.

3.2 AWS S3

AWS S3(서비스)는 Amazon Web Services의 객체 스토리지 서비스로, 데이터를 안전하게 저장하고 검색하는 데 사용됩니다. AWS S3는 확장성이 뛰어나고 비용 효율적이며, 높은 내구성을 제공합니다. 특히 이미지 저장과 관리에 많은 개발자들이 포함하여, 장기적인 스토리지 솔루션으로 널리 사용되고 있습니다.

4. Next.js에서 이미지 업로드 구현하기

Next.js를 사용하여 이미지 업로드 기능을 구현하기 위해 각각 Cloudinary와 AWS S3를 사용하는 방법에 대해 살펴보겠습니다.

4.1 Cloudinary를 이용한 이미지 업로드

4.1.1 Cloudinary 계정 생성하기

먼저, Cloudinary에 회원가입하고 계정을 생성합니다. 계정을 생성하면 API 키와 API 비밀번호를 포함한 계정 정보를 확인할 수 있습니다. 이 정보는 Next.js 애플리케이션에서 Cloudinary API를 사용하여 이미지를 업로드하는 데 필요합니다.

4.1.2 Next.js 프로젝트 설정하기

Next.js 앱을 생성하고 필요한 패키지를 설치합니다.

npx create-next-app my-image-upload-app
cd my-image-upload-app
npm install cloudinary multer multer-storage-cloudinary

4.1.3 업로드 API 만들기

Next.js는 서버리스 기능을 제공하므로 API 라우트를 사용하여 이미지 업로드 API를 생성할 수 있습니다. pages/api/upload.js 파일을 생성하고 다음과 같이 구성합니다.

import { v2 as cloudinary } from 'cloudinary';
import multer from 'multer';
import { CloudinaryStorage } from 'multer-storage-cloudinary';

cloudinary.config({
  cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
  api_key: process.env.CLOUDINARY_API_KEY,
  api_secret: process.env.CLOUDINARY_API_SECRET,
});

const storage = new CloudinaryStorage({
  cloudinary: cloudinary,
  params: {
    folder: 'uploads',
    allowedFormats: ['jpg', 'png', 'jpeg', 'gif'],
  },
});

const upload = multer({ storage });

export default function handler(req, res) {
  upload.single('image')(req, res, () => {
    res.status(200).send(req.file);
  });
}

4.1.4 클라이언트에서 이미지 업로드하기

이제 클라이언트 측에서 이미지를 업로드하는 기능을 추가합니다. pages/index.js 파일을 수정하여 다음과 같이 작성합니다.

import { useState } from 'react';

export default function Home() {
  const [image, setImage] = useState(null);

  const handleImageChange = (event) => {
    setImage(event.target.files[0]);
  };

  const handleSubmit = async (event) => {
    event.preventDefault();
    const formData = new FormData();
    formData.append('image', image);

    const res = await fetch('/api/upload', {
      method: 'POST',
      body: formData,
    });
    const data = await res.json();
    console.log(data);
  };

  return (
    

이미지 업로드

); }

5. AWS S3를 이용한 이미지 업로드

5.1 AWS 계정 생성 및 S3 버킷 만들기

첫 번째 단계로 AWS에 계정을 생성하고 S3 버킷을 만들어야 합니다. 버킷을 만들고, 버킷 정책을 설정하여 파일 업로드에 대한 권한을 부여해야 합니다.

5.2 AWS SDK 설치

다음으로, AWS SDK를 설치합니다.

npm install aws-sdk multer multer-s3

5.3 S3 업로드 API 만들기

Cloudinary와 비슷하게 API 엔드포인트를 만들 수 있습니다. pages/api/upload-s3.js를 생성하고 다음과 같이 구성합니다.

import AWS from 'aws-sdk';
import multer from 'multer';
import multerS3 from 'multer-s3';

AWS.config.update({
  accessKeyId: process.env.AWS_ACCESS_KEY_ID,
  secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
  region: process.env.AWS_REGION,
});

const s3 = new AWS.S3();

const upload = multer({
  storage: multerS3({
    s3: s3,
    bucket: process.env.S3_BUCKET_NAME,
    acl: 'public-read',
    metadata: function (req, file, cb) {
      cb(null, { fieldName: file.fieldname });
    },
    key: function (req, file, cb) {
      cb(null, Date.now().toString() + '-' + file.originalname);
    },
  }),
});

export default function handler(req, res) {
  upload.single('image')(req, res, function (err) {
    if (err) {
      return res.status(500).send(err);
    }
    res.status(200).send(req.file);
  });
}

5.4 S3 클라이언트 업로드 구현하기

클라이언트에서 이미지 업로드를 구현하는 방법은 Cloudinary와 유사합니다. pages/index.js의 코드를 수정하여 S3 API를 사용할 수 있습니다.

import { useState } from 'react';

export default function Home() {
  const [image, setImage] = useState(null);

  const handleImageChange = (event) => {
    setImage(event.target.files[0]);
  };

  const handleUploadS3 = async (event) => {
    event.preventDefault();
    const formData = new FormData();
    formData.append('image', image);

    const res = await fetch('/api/upload-s3', {
      method: 'POST',
      body: formData,
    });
    const data = await res.json();
    console.log(data);
  };

  return (
    

이미지 업로드

); }

6. 이미지 리사이징 및 최적화

Cloudinary와 AWS S3를 이용한 업로드 외에도, 업로드된 이미지를 리사이즈하고 최적화하는 기능이 중요합니다. Cloudinary는 이러한 기능을 내장하고 있어 다양한 파라미터를 통해 이미지의 크기 및 품질을 자동으로 조정할 수 있습니다. AWS S3를 사용할 경우, 업로드 후 Lambda 함수를 이용해 이미지 최적화 작업을 진행할 수 있습니다.

7. 보안 고려사항

이미지 업로드 기능을 구현할 때는 보안 문제가 생길 수 있습니다. 파일 업로드 과정에서 악성 코드가 포함된 파일이 업로드될 수 있기 때문에, 파일 형식을 체크하고, 올바른 인증절차를 적용하는 것이 중요합니다. Cloudinary API와 AWS S3 모두 안전한 인증 방법을 제공합니다.

8. 결론

이번 포스팅에서는 Next.js와 Cloudinary, AWS S3를 이용하여 이미지 업로드 및 미디어 관리를 구현하는 방법에 대해 자세히 알아보았습니다. 이를 통해 웹 애플리케이션에 필요한 강력한 이미지 업로드 기능을 효과적으로 구축할 수 있습니다. 각 플랫폼의 장단점을 고려하여, 프로젝트에 가장 적합한 솔루션을 선택하시길 바랍니다.

10.Nest.js로 RESTful API 구축하기, 블로그 API의 기본 엔드포인트 설정하기 (CRUD)

Nest.js는 현대적인 웹 애플리케이션 개발을 위한 강력한 프레임워크로, TypeScript로 작성되어 있습니다. Nest.js는 다양한 아키텍처와 디자인 패턴을 지원하며, 특히 RESTful API 구축에 유용합니다. 이 글에서는 Nest.js를 사용하여 블로그 API의 기본 엔드포인트를 설정하고, CRUD(Create, Read, Update, Delete) 작업을 구현하는 과정을 살펴보겠습니다.

1. Nest.js 소개

Nest.js는 Angular에서 영감을 받아 만들어진 Node.js의 프레임워크로, 모듈화, 의존성 주입(DI), 그리고 유연한 구조를 제공합니다. Nest.js는 Express.js를 기반으로 하며, 다양한 라이브러리와 미들웨어를 활용하여 강력한 API 서버를 구축할 수 있습니다.

2. Nest.js 설치 및 프로젝트 생성

Nest.js를 사용하기 위해 먼저 Nest CLI를 설치하고, 새로운 프로젝트를 생성해야 합니다. Nest CLI는 Nest.js 프로젝트를 빠르게 생성하고 관리할 수 있는 도구입니다.

npm install -g @nestjs/cli
nest new blog-api

명령어를 입력하면, 새로운 디렉터리가 생성되고 관련 파일과 종속성이 설치됩니다. 프로젝트 디렉터리로 이동하여 서버를 실행할 수 있습니다.

cd blog-api
npm run start

3. 블로그 API 기본 엔드포인트 설계

블로그 API는 여러 개의 엔드포인트를 통해 블로그 게시물을 관리합니다. 기본적인 CRUD 작업을 위한 엔드포인트는 다음과 같습니다.

  • GET /posts: 모든 게시물 조회
  • GET /posts/:id: 특정 게시물 조회
  • POST /posts: 새로운 게시물 생성
  • PUT /posts/:id: 게시물 수정
  • DELETE /posts/:id: 게시물 삭제

4. 게시물 모듈 생성

Nest.js의 모듈 시스템을 이용하여 게시물 모듈을 생성할 수 있습니다. 아래 명령어를 입력하여 게시물 모듈을 생성합니다.

nest generate module posts
nest generate controller posts
nest generate service posts

위 명령어를 통해 posts라는 이름의 모듈, 컨트롤러, 서비스를 생성합니다. 생성된 파일들은 src/posts 디렉터리 안에 위치하게 됩니다.

5. 게시물 DTO(Data Transfer Object) 정의

게시물 데이터의 형식을 정의하기 위해 DTO를 설정합니다. DTO는 클라이언트와의 데이터 교환을 위한 객체로, 데이터의 유효성을 검사하는 데도 사용됩니다. 다음과 같은 게시물 DTO를 정의하겠습니다.

import { IsNotEmpty, IsString } from 'class-validator';

export class CreatePostDto {
    @IsNotEmpty()
    @IsString()
    readonly title: string;

    @IsNotEmpty()
    @IsString()
    readonly content: string;
}

6. 게시물 서비스 구현

게시물 서비스를 사용하여 데이터의 비즈니스 로직을 관리합니다. 서비스는 데이터베이스와의 상호작용, 데이터 처리 등을 담당합니다. 다음은 간단한 게시물 서비스 코드입니다.

import { Injectable, NotFoundException } from '@nestjs/common';
import { CreatePostDto } from './dto/create-post.dto';

@Injectable()
export class PostsService {
    private posts = [];

    create(createPostDto: CreatePostDto) {
        const newPost = {
            id: this.posts.length + 1,
            ...createPostDto,
        };
        this.posts.push(newPost);
        return newPost;
    }

    findAll() {
        return this.posts;
    }

    findOne(id: number) {
        const post = this.posts.find(p => p.id === id);
        if (!post) {
            throw new NotFoundException(`Post with ID ${id} not found`);
        }
        return post;
    }

    update(id: number, updatePostDto: CreatePostDto) {
        const postIndex = this.posts.findIndex(p => p.id === id);
        if (postIndex === -1) {
            throw new NotFoundException(`Post with ID ${id} not found`);
        }
        this.posts[postIndex] = { id, ...updatePostDto };
        return this.posts[postIndex];
    }

    delete(id: number) {
        const postIndex = this.posts.findIndex(p => p.id === id);
        if (postIndex === -1) {
            throw new NotFoundException(`Post with ID ${id} not found`);
        }
        this.posts.splice(postIndex, 1);
        return { message: 'Post deleted successfully' };
    }
}

7. 게시물 컨트롤러 구현

마지막으로, HTTP 요청을 처리하는 게시물 컨트롤러를 구현합니다. 각 엔드포인트에 해당하는 메소드를 정의하여 요청을 처리합니다.

import { Controller, Get, Post, Body, Param, Put, Delete } from '@nestjs/common';
import { PostsService } from './posts.service';
import { CreatePostDto } from './dto/create-post.dto';

@Controller('posts')
export class PostsController {
    constructor(private readonly postsService: PostsService) {}

    @Post()
    create(@Body() createPostDto: CreatePostDto) {
        return this.postsService.create(createPostDto);
    }

    @Get()
    findAll() {
        return this.postsService.findAll();
    }

    @Get(':id')
    findOne(@Param('id') id: string) {
        return this.postsService.findOne(+id);
    }

    @Put(':id')
    update(@Param('id') id: string, @Body() updatePostDto: CreatePostDto) {
        return this.postsService.update(+id, updatePostDto);
    }

    @Delete(':id')
    delete(@Param('id') id: string) {
        return this.postsService.delete(+id);
    }
}

8. API 테스트

API를 테스트하기 위해 Postman 또는 Insomnia와 같은 API 클라이언트를 사용할 수 있습니다. 아래는 각 엔드포인트를 테스트하기 위한 방법입니다.

  • POST /posts: 새 게시물 생성
  • GET /posts: 모든 게시물 조회
  • GET /posts/:id: 특정 게시물 조회
  • PUT /posts/:id: 게시물 업데이트
  • DELETE /posts/:id: 게시물 삭제

8.1 새 게시물 생성 예제

POST /posts
Content-Type: application/json

{
    "title": "첫 번째 게시물",
    "content": "이것은 첫 번째 게시물의 내용입니다."
}

8.2 모든 게시물 조회 예제

GET /posts

8.3 특정 게시물 조회 예제

GET /posts/1

8.4 게시물 수정 예제

PUT /posts/1
Content-Type: application/json

{
    "title": "수정된 게시물 제목",
    "content": "수정된 내용입니다."
}

8.5 게시물 삭제 예제

DELETE /posts/1

9. 결론

이번 글에서는 Nest.js를 사용하여 블로그 API의 기본 CRUD 엔드포인트를 생성하는 방법을 살펴보았습니다. Nest.js는 직관적이고 유연한 API 개발을 가능하게 하며, 간결하고 유지보수하기 쉬운 코드를 작성할 수 있게 도와줍니다. 이제 여러분은 Nest.js를 활용하여 자신의 요구에 맞는 API를 완성해보시기 바랍니다.

10. 다음 단계

이 튜토리얼을 마친 후, 다음 단계로는 데이터베이스와의 연동을 추가하여 영속성을 제공하는 방법을 분석할 수 있습니다. TypeORM 또는 Sequelize와 같은 ORM 라이브러리를 사용하여 데이터베이스에 저장하고 관리하는 방법에 대해 배우는 것도 추천합니다.

29.이미지 업로드 및 미디어 관리, Nest.js에서 파일 업로드 API 구축

이 글에서는 Nest.js 프레임워크를 사용하여 이미지 업로드 및 미디어 관리 기능을 갖춘 API를 구축하는 방법에 대해 단계별로 설명하겠습니다. 또한, 파일 업로드와 관련된 여러 가지 이론적 배경과 이를 구현하기 위한 코드를 포함하겠습니다.

1. Nest.js 소개

Nest.js는 모던 JavaScript와 TypeScript 기반의 웹 애플리케이션 프레임워크입니다. 엔터프라이즈 애플리케이션을 구축하기 위해 필요한 기능을 갖추고 있으며, 효율적인 모듈화와 테스트 용이성을 제공합니다. 기존의 Express.js에 기반하고 있지만, 더 많은 기능과 구조를 제공합니다.

2. 파일 업로드 API 구축의 필요성

파일 업로드는 웹 애플리케이션에서 흔히 필요한 기능 중 하나입니다. 사용자에게 파일을 업로드할 수 있는 기능을 제공함으로써 다양한 서비스—사진 공유, 문서 첨부, 미디어 관리—를 가능하게 합니다. 이러한 API를 통해 사용자는 사용자의 데이터를 서버에 안전하게 저장할 수 있습니다.

3. Nest.js 프로젝트 설정하기

먼저 Nest.js 프로젝트를 설정해야 합니다. 아래 명령어를 사용하여 프로젝트를 생성합니다.

npm i -g @nestjs/cli
nest new media-upload

프로젝트를 생성한 후, 필요한 모듈을 설치합니다. 주요 모듈로는 multer@nestjs/platform-express가 있습니다.

npm install --save multer @nestjs/platform-express

4. 이미지 업로드를 위한 DTO 설계

이미지 업로드를 처리하기 위한 데이터 전송 객체(DTO)를 설계합니다. DTO는 클라이언트와 서버 간에 데이터가 전송될 때의 구조를 정의합니다. 아래는 파일 업로드를 위한 간단한 DTO 예시입니다.

import { IsNotEmpty } from 'class-validator';
        
        export class UploadImageDto {
            @IsNotEmpty()
            readonly image: Express.Multer.File;
        }

5. 파일 업로드를 위한 서비스 구현

이제 파일 업로드를 처리할 서비스를 구현해야 합니다. 이를 위해, ‘upload.service.ts’ 파일을 생성하고 다음의 내용을 구현합니다.

import { Injectable } from '@nestjs/common';
        import { join } from 'path';
        import { v4 as uuid } from 'uuid';
        
        @Injectable()
        export class UploadService {
            async uploadFile(file: Express.Multer.File): Promise {
                const fileName = `${uuid()}-${file.originalname}`;
                const filePath = join(__dirname, '..', '..', 'uploads', fileName);
                // file system API 등을 사용해 파일을 저장
                return filePath;
            }
        }

위의 서비스에서는 UUID를 사용하여 파일 이름을 생성하고, 파일이 업로드될 경로를 정의합니다. 이후 파일 시스템 API를 사용하여 파일을 저장합니다.

6. 파일 업로드를 위한 컨트롤러 구현

이제 사용자가 파일을 업로드할 수 있도록 제어하는 컨트롤러를 작성합니다. 아래는 ‘upload.controller.ts’의 예시입니다.

import { Controller, Post, UseInterceptors, UploadedFile, Body } from '@nestjs/common';
        import { FileInterceptor } from '@nestjs/platform-express';
        import { UploadService } from './upload.service';
        import { UploadImageDto } from './dto/upload-image.dto';
        
        @Controller('uploads')
        export class UploadController {
            constructor(private readonly uploadService: UploadService) {}
        
            @Post('image')
            @UseInterceptors(FileInterceptor('image'))
            async uploadImage(@UploadedFile() file: Express.Multer.File, @Body() uploadImageDto: UploadImageDto) {
                const filePath = await this.uploadService.uploadFile(file);
                return { url: filePath };
            }
        }

위의 컨트롤러에서는 HTTP POST 요청을 처리하여 사용자가 업로드한 이미지 파일을 받아 처리합니다. ‘FileInterceptor’를 사용하여 파일 업로드를 간편하게 처리할 수 있습니다.

7. 업로드 경로 설정하기

파일이 업로드될 경로를 설정하는 방법입니다. Nest.js에서는 multer를 사용하여 파일 업로드를 쉽게 처리할 수 있습니다. 기본적으로 multer의 저장 옵션을 사용하거나, 사용자가 원하는 경로를 지정할 수 있습니다.

import { multerOptions } from './multer.options';

        export const multerOptions = {
            storage: multer.diskStorage({
                destination: (req, file, cb) => {
                    cb(null, 'uploads/');
                },
                filename: (req, file, cb) => {
                    cb(null, `${uuid()}-${file.originalname}`);
                },
            }),
        };
        

8. 미디어 관리 API 구축

이미지 업로드 API 외에도 미디어 관리를 위한 API를 구축하는 방법도 설명하겠습니다. 사용자는 업로드한 미디어 파일을 조회, 삭제할 수 있어야 합니다. 이를 위한 간단한 API를 작성해보겠습니다.

import { Controller, Get, Delete, Param } from '@nestjs/common';

        @Controller('media')
        export class MediaController {
            @Get()
            findAll() {
                // DB에서 모든 미디어 파일 조회
                return mediaList;
            }

            @Delete(':id')
            remove(@Param('id') id: string) {
                // 특정 미디어 파일 삭제 처리
                return { message: 'File removed' };
            }
        }

9. 테스트 및 디버깅

이제 모든 컴포넌트가 준비되었으므로, API를 테스트하고 디버깅하는 단계입니다. Postman 또는 Insomnia와 같은 도구를 사용하여 API를 호출하고, 정상적으로 작동하는지 확인합니다.

10. 마치며

이번 포스트에서는 Nest.js를 사용하여 이미지 업로드 및 미디어 관리 API를 구축하는 방법에 대해 알아보았습니다. Nest.js는 TS 기반 프레임워크로, 멀티미디어 파일 처리를 효율적으로 할 수 있도록 도와줍니다. 이와 같은 기능은 웹 애플리케이션 개발의 필수적인 요소로, 이를 통해 사용자에게 더 나은 경험을 제공할 수 있습니다. 추가적으로, 여러 가지 보안 또는 관리 기능을 추가하여 서비스를 더욱 안전하게 사용할 수 있도록 할 수 있습니다.

작성자: {작성자 이름}

날짜: {작성 날짜}

36.서버 사이드 렌더링(SSR)과 클라이언트 사이드 렌더링(CSR) 비교 및 구현, 페이지별 SSR과 CSR 전략 적용

웹 개발에서 사용되는 두 가지 주요 렌더링 방식인 서버 사이드 렌더링(SSR)과 클라이언트 사이드 렌더링(CSR)은 각기 다른 방법으로 웹 페이지를 렌더링합니다. 이 글에서는 이 두 가지 방식의 차이를 비교하고, Nest.js와 Next.js를 활용하여 실제 구현 예시를 제공하겠습니다. 또한, 페이지별 전략을 선택하는 방법에 대해서도 논의하겠습니다.

1. 서버 사이드 렌더링(SSR)란?

서버 사이드 렌더링(SSR)은 웹 페이지의 모든 콘텐츠가 서버에서 렌더링되어 클라이언트(브라우저)로 전달되는 방식입니다. 사용자가 페이지를 요청하면 서버는 HTML을 생성하고 이를 클라이언트에 반환합니다. 클라이언트는 반환된 HTML을 화면에 표시하게 됩니다.

SSR의 장점

  • SEO 최적화: 검색 엔진은 서버에서 렌더링된 페이지를 분석하여 색인화할 수 있습니다.
  • 빠른 초기 로드: 서버에서 완전한 HTML이 제공되므로 사용자는 콘텐츠를 빨리 볼 수 있습니다.
  • 더 나은 사용자 경험: 화면 깜빡임이 줄어들어 사용자가 느끼는 안정성이 높습니다.

SSR의 단점

  • 서버 부하 증가: 모든 요청에 대해 서버가 렌더링을 수행해야 하므로 서버의 부하가 증가합니다.
  • 컨텐츠 반응성 감소: 사용자 상호작용에 대한 실시간 반응성이 떨어질 수 있습니다.

2. 클라이언트 사이드 렌더링(CSR)란?

클라이언트 사이드 렌더링(CSR)은 웹 페이지의 내용을 클라이언트(브라우저)에서 JavaScript를 통해 렌더링하는 방식입니다. 사용자가 페이지를 요청할 때, 서버는 최소한의 HTML과 JavaScript 파일을 반환하고, 클라이언트는 JavaScript를 실행하여 동적으로 HTML을 생성합니다.

CSR의 장점

  • 서버 부하 감소: 서버는 정적 파일만 제공하므로 부하가 줄어듭니다.
  • 빠른 페이지 전환: 페이지 간 전환 시 새로고침 없이 콘텐츠가 업데이트됩니다.
  • 인터랙티브한 UI: 사용자 상호작용에 대한 더 나은 반응성을 제공합니다.

CSR의 단점

  • SEO 문제: 검색 엔진이 JavaScript를 제대로 렌더링하지 못할 경우 SEO에 불리할 수 있습니다.
  • 느린 초기 로드: 첫 페이지 로딩 시 많은 JavaScript 파일을 다운로드해야 하므로 초기 로드 시간이 길어질 수 있습니다.
  • 웹 브라우저 요구: JavaScript가 비활성화된 환경에서는 콘텐츠를 표시할 수 없습니다.

3. SSR과 CSR의 비교

특징 서버 사이드 렌더링 (SSR) 클라이언트 사이드 렌더링 (CSR)
SEO 최적화 우수함 제한적
초기 로드 속도 빠름 느림
서버 부하 증가함 감소함
상호작용 성능 제한적 우수함

4. Nest.js와 Next.js를 활용한 SSR 및 CSR 구현

Nest.js는 서버 사이드 애플리케이션을 위한 프레임워크이며, Next.js는 React 기반의 SSR 및 CSR을 지원하는 프레임워크입니다. 두 프레임워크를 결합하여 SSR 및 CSR을 구현하는 방법에 대해 설명하겠습니다.

4.1. Nest.js 설정

npm install -g @nestjs/cli
nest new project-name
cd project-name
npm install @nestjs/platform-express
npm install @nestjs/core

Nest.js를 사용하여 서버를 구성한 후, 요청에 따라 SSR로 HTML을 렌더링할 수 있는 엔드포인트를 생성합니다.

@Get('/api/users')
async getUsers() {
    return await this.userService.findAll();
}

4.2. Next.js 설정

npx create-next-app name-of-your-app
cd name-of-your-app
npm run dev

Next.js에서 페이지를 생성하고, 데이터를 가져올 수 있는 SSR과 CSR 메서드를 구현합니다.

4.2.1. SSR 구현

export async function getServerSideProps() {
    const res = await fetch('http://localhost:3000/api/users');
    const data = await res.json();

    return { props: { users: data } };
}

const UsersPage = ({ users }) => {
    return (
        
{users.map(user => (
{user.name}
))}
); }; export default UsersPage;

4.2.2. CSR 구현

import { useEffect, useState } from 'react';

const UsersPage = () => {
    const [users, setUsers] = useState([]);

    useEffect(() => {
        const fetchUsers = async () => {
            const res = await fetch('http://localhost:3000/api/users');
            const data = await res.json();
            setUsers(data);
        };
        fetchUsers();
    }, []);

    return (
        
{users.map(user => (
{user.name}
))}
); }; export default UsersPage;

5. 페이지별 SSR과 CSR 전략 적용하기

웹 애플리케이션을 개발할 때 모든 페이지에서 동일하게 SSR 또는 CSR을 사용할 필요는 없습니다. 페이지마다 다른 전략을 적용하여 최적의 사용자 경험을 제공할 수 있습니다. 예를 들어, 사용자가 자주 조회하는 대시보드 페이지는 CSR을 사용하여 상호작용을 최적화하고, 블로그 페이지는 SEO를 고려하여 SSR을 사용할 수 있습니다.

5.1. 페이지별 전략 결정 기준

  • SEO 중요성: 사용자 검색에 의해 노출되는 페이지는 SSR을 사용하는 것이 유리합니다.
  • 상호작용 필요성: 유저 인터랙션이 많은 페이지는 CSR을 사용하여 반응성을 높입니다.
  • 데이터의 동적 여부: 실시간 데이터가 필요한 경우 CSR을 사용하는 것이 좋습니다.
  • 로드 시간에 대한 요구: 초기 로드 시간이 중요한 경우 SSR을 통해 더 빠른 로드 속도를 제공할 수 있습니다.

결론

서버 사이드 렌더링(SSR)과 클라이언트 사이드 렌더링(CSR)은 웹 애플리케이션에서 중요한 역할을 수행하며, 각각의 장단점이 존재합니다. Nest.js와 Next.js를 활용하면 두 가지 렌더링 방식을 모두 쉽게 구현할 수 있습니다. 페이지별 전략을 신중하게 선택함으로써, 사용자에게 최적화된 경험을 제공할 수 있습니다. 이 가이드를 통해 SSR과 CSR을 이해하고 실제 애플리케이션에 적용해 보시기 바랍니다.