[Next.js 13] 풀스택으로 블로그 만들기 1. 파일 시스템을 활용한 서비스 로직

2023. 12. 21. 04:04프론트엔드/Next.js

반응형

블로그 썸네일 ver3_복사본 (2)-001 (3).png

이전 포스팅에서 어떻게 진행할 지 알아보았다.
( https://aboveimagine.tistory.com/128 )
이제 코드를 작성해볼 차례!
물론 내 코드가 절대 정답은 아닐 것이므로
개선해야할 부분이 있다면 댓글로 알려주시길 🙏🏻

마크다운 파일의 메타데이터 관리

Front matter

내 로컬 환경의 마크다운 파일을 이용하여 블로그를 만들려면
해당 마크다운 파일에 대한 상세 정보가 필요하다.
즉, 제목, 작성일, 카테고리 등등의 정보를 미리 마크다운 파일 상단에 작성해주면 된다.
이렇게 마크다운(markdown) 파일이나 다른 텍스트 기반 파일의 상단에 위치하며, 해당 파일에 대한 메타데이터(metadata)를 정의하는 부분을 Front matter 라고 한다.

---
title: '제목'
date: '2023-12-10'
---

gray-matter

gray-matter 라이브러리를 이용하면 작성한 front matter를 객체 형태로 바꿔줄 수 있다.

---
title: Hello
slug: home
---
<h1>Hello world!</h1>
{
  content: '<h1>Hello world!</h1>',
  data: {
    title: 'Hello',
    slug: 'home'
  }
}

설치

$ npm install --save gray-matter

사용

import matter from "gray-matter";
console.log(matter('---\ntitle: Front Matter\n---\nThis is content.'));

// 결과
{
  content: 'This is content.',
  data: {
    title: 'Front Matter'
  }
}

서비스 로직 구성

다음은 파일 시스템을 이용해 마크다운 파일을 읽고 쓰는 함수를 만들어야 한다.
브라우저에서는 직접적으로 파일 시스템에 접근할 수 있는 권한이 없다.
따라서 서버사이드에서 Node.js의 fs 모듈을 이용해야 한다.

서비스 로직 종류

src/service/posts.ts 안에 서비스 로직을 구성한다. (CRUD)

  • fsGetPostsList() : 전체 md 파일 조회
  • fsGetPostDetail() : 특정 md 파일 상세 조회
  • fsCreatePost() : md 파일 생성
  • fsUpdatePost() : md 파일 수정
  • fsDeletePost() : md 파일 삭제

나중에 프론트 쪽 함수와 헷갈릴 것 같아서 fs로 시작하게끔 함수명을 정했다.
그리고 fs 모듈의 함수인 readdir(), readFile(), writeFile(), unlink() 등을 이용하여 로직을 작성했다.
일례로 전체 파일 조회 코드만 살펴보면 아래와 같다.

import path from "path";
import { promises as fs } from "fs";
import matter from "gray-matter";
import { NewPost, Post, ApiResponse } from "@/types/post";

const postsDirectory = path.join(process.cwd(), "public/posts");

export async function fsGetPostsList(): Promise<ApiResponse<Post[]>> {
	try {
		const fileNames = await fs.readdir(postsDirectory);
		const postsList = await Promise.all(
			fileNames.map(async (fileName) => {
				// Remove ".md" from file name to get id
				const id = fileName.replace(/\.md$/, "");
				// Read markdown file as string
				const fullPath = path.join(postsDirectory, fileName);
				const fileContents = await fs.readFile(fullPath, "utf8"); // 비동기 메서드로 변경
				const matterResult = matter(fileContents);
				const result = { id, ...matterResult.data };
				return result as unknown as Post;
			})
		);
		return { success: true, data: postsList };
	} catch (error) {
		return { success: false, error: `Error reading posts list: ${error}` };
	}
}

이런 방식으로 위의 CRUD 로직들을 작성해보았다.
이 함수들의 return 값은 response에 해당하므로,
success (성공 실패 여부), data (성공 시 해당 데이터), error (실패 시 에러 메세지)로 이루어진 객체를 리턴하게끔 했다.

이렇게 작업을 하고보니 결국은 이게 일반적으로 백엔드 서버에서 하는 역할이고,
내 로컬 파일이 DB겠구나 하는 감이 이제야 왔다.
그럼 이제 이 요청을 전달할 API는 다음 편에서 계속 🫡

반응형