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

21.Nest.js와 Next.js에서 Middleware와 Guard 사용하기, 각 페이지별 접근 권한 설정

본 블로그 포스트에서는 Nest.js와 Next.js에서 Middleware와 Guard의 개념과 그 사용 방법에 대해 자세히 설명하겠습니다. 특히, 각 페이지별 접근 권한을 설정하는 방법에 대해서도 알아보겠습니다. 이 포스트를 통해 여러분은 Nest.js와 Next.js 환경에서 권한을 관리하고, 사용자 인증 및 인가를 효과적으로 구현할 수 있는 방법을 익히게 될 것입니다.

1. Nest.js에서 Middleware 이해하기

Nest.js는 Express.js 기반의 훌륭한 Node.js 프레임워크로, Middleware는 요청-응답 사이클에서 요청을 처리하는 중간 단계의 로직을 구성하는 데 사용됩니다. Middleware는 요청을 가로채어 처리하거나, 요청 객체를 변형하거나, 다음 Middleware로 요청을 전달할 수 있는 기능을 제공합니다.

1.1 Middleware의 용도

  • 로그인 여부 확인
  • 요청 기록 기록
  • 헤더 수정
  • 오류 처리

1.2 Nest.js에서 Middleware 구현하기

Nest.js에서는 Middleware를 클래스로 구현하고, 이를 모듈에서 등록하여 사용할 수 있습니다. 아래는 간단한 로그인 확인 Middleware의 예시입니다.


import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class AuthMiddleware implements NestMiddleware {
    use(req: Request, res: Response, next: NextFunction) {
        const token = req.headers.authorization;
        if (token) {
            // 인증 로직 (예: 디코딩 JWT)
            next();
        } else {
            res.status(403).send('Unauthorized');
        }
    }
}

위의 코드에서 `AuthMiddleware`는 요청에 포함된 토큰을 확인하고, 인증이 이루어지지 않은 경우 403 상태 코드를 반환합니다.

1.3 Middleware 등록하기

Middleware는 모듈에서 등록할 수 있습니다. 아래는 전체 모듈에 Middleware를 등록하는 방법입니다.


import { Module, MiddlewareConsumer, NestModule } from '@nestjs/common';
import { AuthMiddleware } from './auth.middleware';

@Module({
    // ...
})
export class AppModule implements NestModule {
    configure(consumer: MiddlewareConsumer) {
        consumer
            .apply(AuthMiddleware)
            .forRoutes('*'); // 모든 경로에 Middleware 적용
    }
}

2. Nest.js에서 Guard 사용하기

Guard는 요청 처리 전에 특정 조건(예: 권한)을 확인하는 데 사용됩니다. Guards는 다음 두 가지 주요 목적으로 사용됩니다.

  • 특정 경로 또는 핸들러에 대한 접근 권한 확인
  • 어떠한 요청을 처리할 수 있는지에 대한 조건을 설정

2.1 Guard의 정의 및 사용 방법

Nest.js의 Guards는 주로 `CanActivate` 인터페이스를 구현하여 생성합니다. 아래는 사용자의 역할 기반으로 접근을 제어하는 Guard의 예시입니다.


import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';

@Injectable()
export class RolesGuard implements CanActivate {
    canActivate(context: ExecutionContext): boolean {
        const request = context.switchToHttp().getRequest();
        const user = request.user; // 이전에 middleware에서 사용자 정보 설정
        const hasRole = () => user.roles.indexOf('admin') > -1;
        return user && user.roles && hasRole();
    }
}

2.2 Guard 등록 및 사용하기

Guard는 컨트롤러의 핸들러 또는 모듈에 더할 수 있습니다. 아래의 코드는 특정 컨트롤러에 Guard를 등록하는 예시입니다.


import { Controller, Get, UseGuards } from '@nestjs/common';
import { RolesGuard } from './roles.guard';

@Controller('admin')
export class AdminController {
    @Get()
    @UseGuards(RolesGuard)
    getAdminData() {
        return 'Admin Data';
    }
}

3. Next.js에서 Middleware 이해하기

Next.js에서 Middleware는 요청이 들어오는 단계에서 선처리하거나 특정 조건에 따라 사용자에게 응답할 수 있는 방법을 제공합니다. Next.js는 API Routes와 페이지에 Middleware를 적용할 수 있습니다.

3.1 Next.js Middleware의 용도

  • 사용자 인증
  • 404 처리
  • API 호출 간소화

3.2 Next.js에서 Middleware 활성화하기

Next.js 12 버전부터 제공되는 Middleware를 사용하여 각 라우트에 접근하기 전에 사용자 인증을 확인하는 예시입니다. Middleware는 `middleware.ts` 파일에서 정의합니다.


// middleware.ts
import { NextResponse } from 'next/server';

export function middleware(request) {
    const token = request.cookies.get('token');
    if (!token) {
        return NextResponse.redirect(new URL('/login', request.url));
    }
    return NextResponse.next();
}

4. Next.js에서 Guard 사용하기

Next.js에서는 Guard를 명시적으로 구현하는 방법은 없지만, 각 페이지에서 직접 사용자 인증을 처리할 수 있습니다. 예를 들어, 서버 사이드 렌더링(SSR)에서 페이지 접근 제어를 설정할 수 있습니다.

4.1 서버 사이드에서 접근 권한 검증하기

아래는 Next.js의 `getServerSideProps`를 사용하여 사용자의 인증 상태를 확인하는 예시입니다.


export async function getServerSideProps(context) {
    const { req } = context;
    const token = req.cookies.token;
    
    if (!token) {
        return {
            redirect: {
                destination: '/login',
                permanent: false,
            },
        };
    }

    // 사용자 데이터 fetch 등의 로직
    return {
        props: {}, // 성공적으로 인증된 경우 페이지에 전달할 데이터
    };
}

5. 결론

Nest.js와 Next.js에서 Middleware와 Guard를 활용함으로써 애플리케이션의 보안 및 유연성을 높일 수 있습니다. 각각의 기술 스택에서의 역할과 사용법을 깊이 있게 이해하고 있으면, 복잡한 인증 로직과 접근 권한 관리를 보다 효과적으로 구현할 수 있습니다. 이 포스트에서 다룬 내용을 바탕으로 여러분의 프로젝트에도 적절한 Middleware와 Guard를 적용하여 사용자 경험을 향상시키시길 바랍니다.

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을 이해하고 실제 애플리케이션에 적용해 보시기 바랍니다.

6.Next.js와 Nest.js 기본 프로젝트 구조 만들기, Next.js의 페이지와 Nest.js의 모듈 구성하기

JavaScript 생태계에서 Next.js와 Nest.js는 각각 프론트엔드와 백엔드 개발에 혁신적인 접근 방식을 제공합니다. Next.js는 React 기반의 프레임워크로, 서버 사이드 렌더링(SSR)과 정적 웹 사이트 생성을 간편하게 처리할 수 있도록 도와줍니다. 반면, Nest.js는 Node.js의 기반 위에 구축된 프로그레시브한 백엔드 프레임워크로, 모듈화와 클래스 기반의 아키텍처를 강조합니다. 본 강좌에서는 Next.js와 Nest.js의 기본 프로젝트 구조를 만드는 방법과 각각의 페이지 및 모듈 구성하는 방법에 대해 자세히 알아보겠습니다.

1. Next.js 프로젝트 구조 이해하기

Next.js 프로젝트는 일반적으로 다음과 같은 구조를 가집니다:

    my-next-app/
    ├── public/
    ├── pages/
    ├── components/
    ├── styles/
    ├── package.json
    └── next.config.js
    

여기서 각 디렉토리와 파일의 역할은 다음과 같습니다:

  • public/: 정적 파일(이미지, 아이콘 등)을 저장하는 디렉토리입니다. 이곳에 있는 파일은 “/” 경로로 접근할 수 있습니다.
  • pages/: Next.js에서 가장 중요한 디렉토리로, 애플리케이션의 각 페이지를 정의합니다. 파일 이름이 URL 경로에 직접적으로 매핑됩니다. 예를 들어, pages/index.js는 루트 페이지(/)를 나타냅니다.
  • components/: 여러 페이지에서 재사용할 수 있는 React 컴포넌트들을 저장하는 디렉토리입니다.
  • styles/: CSS 파일이나 스타일 관련 모듈을 여기에 보관합니다. Next.js는 CSS와 Sass를 지원합니다.
  • package.json: 프로젝트의 메타데이터와 의존성을 정의합니다.
  • next.config.js: Next.js의 다양한 설정을 구성할 수 있는 파일입니다.

2. Nest.js 프로젝트 구조 이해하기

Nest.js 프로젝트는 다음과 같은 구조를 가집니다:

    my-nest-app/
    ├── src/
    │   ├── app.module.ts
    │   ├── main.ts
    │   └── /
    │       ├── .module.ts
    │       ├── .controller.ts
    │       └── .service.ts
    ├── test/
    ├── package.json
    └── tsconfig.json
    

각 디렉토리와 파일의 역할은 다음과 같습니다:

  • src/: 애플리케이션의 모든 소스 코드를 포함합니다. Nest.js의 핵심 로직이 여기에 위치합니다.
  • app.module.ts: 애플리케이션의 루트 모듈입니다. 모든 모듈을 조합하는 역할을 합니다.
  • main.ts: 애플리케이션의 진입점으로, Nest.js 서버를 시작하는 파일입니다.
  • /: 특정 모듈을 위한 디렉토리입니다. 각 모듈은 controller, service 및 다른 관련 파일들을 포함할 수 있습니다.
  • test/: 테스트 관련 파일을 저장하는 디렉토리입니다.
  • package.json: 프로젝트의 메타데이터와 의존성을 정의합니다.
  • tsconfig.json: TypeScript 컴파일러 설정 파일입니다.

3. Next.js 프로젝트 만들기

먼저 Next.js 프로젝트를 만듭니다. 터미널에서 다음 명령어를 실행하세요:

npx create-next-app my-next-app

이 명령어는 Next.js의 템플릿을 기반으로 새로운 프로젝트를 생성합니다. 생성된 my-next-app 디렉토리로 이동한 후 실행하면 기본 Next.js 애플리케이션을 확인할 수 있습니다:

cd my-next-app
npm run dev

브라우저를 열고 http://localhost:3000에 접속하면 기본 페이지를 확인할 수 있습니다.

4. Nest.js 프로젝트 만들기

Nest.js 프로젝트를 생성하기 위해 Nest CLI를 사용합니다. 먼저 Nest CLI를 설치합니다:

npm i -g @nestjs/cli

설치가 완료되면 새로운 Nest.js 프로젝트를 생성합니다:

nest new my-nest-app

생성된 my-nest-app 디렉토리로 이동하여 서버를 실행합니다:

cd my-nest-app
npm run start

브라우저를 열고 http://localhost:3000에 접속하면 기본 Nest.js 애플리케이션을 확인할 수 있습니다.

5. Next.js의 페이지 구성하기

Next.js에서 페이지를 구성하기 위해 pages/ 디렉토리에 새로운 JavaScript 파일을 추가합니다. 예를 들어, pages/about.js를 생성하고 다음 코드를 작성합니다:

import React from 'react';

    const About = () => {
        return (
            

About Us

이곳은 우리의 소개 페이지입니다.

); }; export default About;

웹 브라우저에서 http://localhost:3000/about를 열면 방금 작성한 소개 페이지를 확인할 수 있습니다. Next.js는 파일 기반 라우팅을 지원하기 때문에, 다른 페이지를 추가하는 것도 매우 쉬운 작업입니다.

6. Nest.js의 모듈 구성하기

Nest.js에서는 모듈을 통해 애플리케이션을 구성합니다. 예를 들어, ‘users’ 모듈을 생성하고 구성하는 방법을 보겠습니다. 터미널에서 다음 명령어를 실행하여 모듈을 생성합니다:

nest generate module users
nest generate controller users
nest generate service users

이 명령어는 src/users/ 디렉토리를 생성하고, 사용자 관리에 필요한 서비스 및 컨트롤러를 자동으로 생성합니다. src/users/users.module.ts 파일을 다음과 같이 수정합니다:

import { Module } from '@nestjs/common';
    import { UsersController } from './users.controller';
    import { UsersService } from './users.service';

    @Module({
        controllers: [UsersController],
        providers: [UsersService],
    })
    export class UsersModule {}

이제 src/users/users.controller.tssrc/users/users.service.ts 파일을 수정하여 컨트롤러와 서비스의 로직을 추가할 수 있습니다. 아래는 사용자 목록을 반환하는 간단한 서비스와 컨트롤러의 예입니다.

import { Controller, Get } from '@nestjs/common';
    import { UsersService } from './users.service';

    @Controller('users')
    export class UsersController {
        constructor(private readonly usersService: UsersService) {}

        @Get()
        getAllUsers() {
            return this.usersService.findAll();
        }
    }
    
import { Injectable } from '@nestjs/common';

    @Injectable()
    export class UsersService {
        private readonly users = ['Alice', 'Bob', 'Charlie'];

        findAll() {
            return this.users;
        }
    }
    

위의 코드를 바탕으로 Nest.js 애플리케이션에서 사용자 목록을 반환하는 API를 구성할 수 있습니다. 브라우저에서 http://localhost:3000/users에 접속하면 사용자 목록을 확인할 수 있습니다.

7. Next.js와 Nest.js를 함께 사용하기

Next.js와 Nest.js를 함께 사용하여 풀스택 애플리케이션을 구축할 수 있습니다. 이러한 구조에서는 Next.js가 프론트엔드 부분을 담당하고, Nest.js가 백엔드 API를 제공하는 역할을 수행합니다. 다음 단계에서는 두 애플리케이션이 서로 통신할 수 있도록 설정합니다.

7.1. Nest.js API 서버 설정

먼저 Nest.js 애플리케이션을 다음과 같이 설정하여 CORS를 활성화합니다:

import { NestFactory } from '@nestjs/core';
    import { AppModule } from './app.module';

    async function bootstrap() {
        const app = await NestFactory.create(AppModule);
        app.enableCors();
        await app.listen(3000);
    }
    bootstrap();

7.2. Next.js에서 API 호출하기

Next.js 애플리케이션에서 Nest.js API를 호출하려면 데이터 패칭을 위해 axios와 같은 라이브러리를 사용할 수 있습니다. pages/index.js 파일을 다음과 같이 수정합니다:

import React, { useEffect, useState } from 'react';
    import axios from 'axios';

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

        useEffect(() => {
            const fetchUsers = async () => {
                const response = await axios.get('http://localhost:3000/users');
                setUsers(response.data);
            };
            fetchUsers();
        }, []);

        return (
            

사용자 목록

    {users.map(user => (
  • {user}
  • ))}
); }; export default Home;

이제 Next.js 애플리케이션의 홈 페이지에서 Nest.js API를 통해 사용자 목록을 가져오는 기능이 추가되었습니다.

8. 결론

Next.js와 Nest.js를 함께 사용하여 풀스택 애플리케이션을 구축하는 방법에 대해 살펴보았습니다. 각 프레임워크의 프로젝트 구조와 구성 방법을 이해하고, 두 애플리케이션 간의 데이터 통신을 설정하는 과정을 통해, 여러분은 효율적이고 강력한 애플리케이션을 개발할 수 있습니다.

이번 강좌를 통해 Next.js와 Nest.js의 기본적인 사용법뿐만 아니라, 실제로 프로젝트를 설정하고 페이지 및 모듈을 구성하는 방법을 배웠습니다. 앞으로 이 두 기술을 바탕으로 더욱 발전된 애플리케이션을 만들어보시기를 바랍니다.

27.블로그 포스트 작성 및 에디터 기능 추가, 서버에서 마크다운 파일을 HTML로 변환하여 렌더링

현대 웹 애플리케이션 개발에서 블로그 시스템은 많은 인기를 끌고 있습니다. 특히나 개인이 자신을 표현할 수 있는 공간으로 블로그는 매우 중요한 역할을 합니다. 이번 포스트에서는 Nest.js와 Next.js를 활용하여 블로그 포스트 작성 및 에디터 기능을 추가하는 방법에 대해 논의하고자 합니다.

1. 프로젝트 구성

Nest.js는 서버 사이드 프레임워크로, TypeScript로 작성되어 있으며, Next.js는 리액트 기반의 서버 사이드 렌더링(SSR) 프레임워크입니다. 이 두 프레임워크를 조합하여 효율적인 블로그 애플리케이션을 구현할 수 있습니다. 먼저, Nest.js를 사용하여 API 서버를 구축하고, Next.js로 클라이언트 애플리케이션을 만듭니다.

2. Nest.js API 서버 구축

Nest.js에서 API를 구축하는 기본적인 과정은 다음과 같습니다. 먼저, Nest.js CLI를 사용하여 프로젝트를 생성합니다.

npm i -g @nestjs/cli
nest new blog-server

생성된 프로젝트 폴더로 이동한 후, 필요한 패키지를 설치합니다. Markdown 파일을 처리하기 위해 `marked` 패키지를 사용할 것입니다.

npm install marked

다음으로, 포스트를 관리할 수 있는 기본적인 CRUD API를 만듭니다. `posts`라는 모듈을 생성합니다.

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

2.1. 포스트 DTO(Data Transfer Object) 정의

포스트 데이터를 다루기 위해 DTO를 만듭니다. `create-post.dto.ts` 파일을 생성하고 아래와 같이 작성합니다.

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

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

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

2.2. 서비스 구현

`posts.service.ts` 파일을 열고 포스트 데이터를 관리하는 메서드를 추가합니다. 여기서는 간단히 메모리에 데이터를 저장합니다.

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

        interface Post {
            id: number;
            title: string;
            content: string;
        }

        @Injectable()
        export class PostsService {
            private posts: Post[] = [];
            private idCounter = 1;

            create(postDto: CreatePostDto): Post {
                const newPost = {
                    id: this.idCounter++,
                    ...postDto,
                };
                this.posts.push(newPost);
                return newPost;
            }

            findAll(): Post[] {
                return this.posts;
            }

            findOne(id: number): Post {
                return this.posts.find(post => post.id === id);
            }
        }

2.3. 컨트롤러 구현

`posts.controller.ts` 파일을 열고 API 엔드포인트를 추가합니다. 포스트를 추가하고, 목록을 가져오는 기능을 구현합니다.

import { Controller, Post, Body, Get } 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() postDto: CreatePostDto) {
                return this.postsService.create(postDto);
            }

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

3. 클라이언트 애플리케이션 Next.js 구축

이제 Next.js를 사용하여 블로그 클라이언트를 구축하겠습니다. 새로운 Next.js 프로젝트를 생성합니다.

npx create-next-app blog-client

클라이언트 애플리케이션에서도 Markdown 파일을 사용하기 위해 `remark` 패키지를 설치합니다.

npm install remark remark-html

3.1. 포스트 작성 페이지

포스트를 작성할 수 있는 페이지를 만들어야 합니다. `pages/create-post.js` 파일을 생성하고 아래와 같이 작성합니다.

import { useState } from 'react';

        export default function CreatePost() {
            const [title, setTitle] = useState('');
            const [content, setContent] = useState('');

            const handleSubmit = async (e) => {
                e.preventDefault();
                const response = await fetch('http://localhost:3000/posts', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify({ title, content }),
                });
                if (response.ok) {
                    alert('포스트가 성공적으로 작성되었습니다.');
                }
            };

            return (
                
setTitle(e.target.value)} required />
); }

4. 마크다운 처리 및 HTML 렌더링

사용자가 작성한 마크다운 내용을 HTML로 변환하여 렌더링하는 기능을 추가해야 합니다. 이 과정에서 `remark`와 `remark-html`을 사용하여 마크다운을 HTML로 변환합니다.

4.1. Markdown to HTML 변환 구현

포스트를 조회하는 페이지를 만들어 HTML로 변환된 내용을 렌더링합니다. `pages/posts/[id].js` 파일을 생성하고 아래와 같이 작성합니다.

import { useEffect, useState } from 'react';
        import { remark } from 'remark';
        import html from 'remark-html';

        const Post = ({ post }) => {
            const [contentHtml, setContentHtml] = useState('');

            useEffect(() => {
                const convertMarkdownToHtml = async () => {
                    const processedContent = await remark()
                        .use(html)
                        .process(post.content);
                    setContentHtml(processedContent.toString());
                };
                convertMarkdownToHtml();
            }, [post.content]);

            return (
                

{post.title}

); }; export async function getServerSideProps(context) { const { id } = context.params; const res = await fetch(`http://localhost:3000/posts/${id}`); const post = await res.json(); return { props: { post } }; } export default Post;

5. 전체 흐름 정리

이제 사용자가 블로그 포스트를 작성하고, 이를 마크다운 형식으로 저장하고, 이후 이를 HTML로 변환하여 렌더링할 수 있는 전체적인 기능을 갖춘 시스템이 완성되었습니다. Nest.js는 백엔드에서 포스트를 저장하고 관리하며, Next.js는 클라이언트에서 포스트 작성과 내용을 표시하는 역할을 합니다. 이러한 시스템 구성은 개발의 유연성을 높여주며, 유지보수 또한 쉽게 만들어줍니다.

6. 결론

블로그 포스트 작성 및 에디터 기능을 추가하는 과정에서 Nest.js와 Next.js를 활용한 다양한 예제를 살펴보았습니다. 마크다운을 HTML로 변환하는 과정은 블로그 개발에서 매우 중요한 부분이며, 이를 통해 사용자에게 더 나은 콘텐츠를 제공할 수 있습니다. 앞으로 자신만의 블로그를 구축하는 데 있어 Nest.js와 Next.js를 적극 활용해보세요!

저자: 조광형

게시일: 2024년 11월 26일