React

Creating a Recipe Book with Strapi 실습

Asset Type
strapi
File Type
When to use
2022/03/23
Reference
Created time
2022/03/23 12:31
Created by
Last edited time
2022/05/07 13:12
Strapi는 네가 프로젝트의 백엔드를 쉽게 구성하고 관리하는 것을 도와주는 자바스크립트 기반 headless CMS입니다. 프론트엔드에서 너가 원하는 어떤 기술이든지 쓸 수 있다.
그것은 하이브리드 정적 서버 렌더링을 제공하는, 타입스크립트를 지원하고, 파일시스템 라우팅, 부가 기능들을 제공하는 최신 리액트 프레임워크 Next.js와 매우 잘 동작한다.
이 기사에서는, 가상의 레시피 북을 만듦으로써 함께 이 versatile(다용도) 기술들을 사용하는 방법을 배워볼 것입니다.
마지막으로, 당신은 풀스택 앱을 구성하기 위해 Next.js와 Strapi를 사용하는 방법을 알게 될 것입니다.
만약 전체적으로 이 튜토리얼의 코드를 보고 싶다면 여길 확인해라.
이걸 따라하고 싶거나 확장해서 당신이 원하는 걸 만들고 싶으면 fork하거나 clone하세요.
주의:
이 프로젝트를 fork하는 경우 이 문서의 뒷부분에 설명된 대로 레시피와 카테고리를 만든 다음 그에 따라 권한을 설정하십시오. 이러한 단계를 완료하지 않으면 앱을 그대로 진행하지 않으면 오류가 발생합니다.

Setting Up Strapi

Strapi를 설정하려면 먼저 Node.js와 npm을 설치해야 합니다.
그런 다음 다음을 수행합니다.
1.
터미널에서 mkdir strapi-recipes를 실행하여 프로젝트를 위한 폴더를 만든 후 해당 폴더에 cd를 입력합니다.
2.
npx create-strapi-app@latest backend --quickstart를 사용하여 Strapi 프로젝트를 만듭니다.
주의: 빠른 시작 설치는 SQLite 데이터베이스를 사용하여 Strapi를 설정합니다. 다른 데이터베이스 및 설치 옵션을 사용할 수 있습니다(CLI 설치 가이드 참조).
3.
로컬 서버가 자동적으로 시작되는 것을 확인합니다. localhost: 1337/admin으로 이동하여 이메일 주소를 사용하여 관리자 패널에 계정을 만듭니다.

Setting Up Next.js

strapi-recipes 폴더로 이동해서 npx create-next-app frontend를 실행합니다.
frontend 폴더로 가서 npm run dev로 앱을 실행합니다.
strapi는 1337포트에서, Next.js는 3000포트에서 실행됩니다.

Implementing the Recipe Book

프런트엔드와 백엔드가 모두 실행되었으므로 이제 재미있는 부분을 살펴보겠습니다.
레시피북은 먼저 레시피 카테고리와 함께 레시피 자체를 나타내는 콘텐츠 타입을 Strapi에 작성합니다.
그런 다음 Next.js를 사용하여 레시피북의 외관을 디자인하고 Strapi의 내장 REST API를 사용하여 프런트엔드와 백엔드를 연결합니다.

Creating the Collection Types

컬렉션 타입을 작성하려면 관리 패널의 [PLUGINS]> [ Content - Types Builder ]으로 이동합니다. 그런 다음 COLLECTION TYPES에서 Create new collection type을 클릭합니다.
모달의 Display name 아래에 recipe를 입력한 다음 continue를 클릭합니다. 그러면 필드에 대해 다른 옵션이 표시됩니다. 각 레시피에 대해 name, a list of ingredients, intructions, photo와 recipe category(예: breakfast, soups, bread)가 필요하므로 각 레시피에 대한 필드를 추가해야 합니다.
Text를 선택하여 첫 번째 필드를 추가합니다. 이름 필드에 Name을 입력합니다. 이것이 당신의 요리법 이름이 될 것입니다. 그런 다음 Add another field 버튼을 클릭합니다. 이번에는 Rich text를 선택합니다. [Name] 필드에 ingredients를 입력합니다. 동일한 절차에 따라 instructions라는 Rich text 필드와 photo라는 Media 필드를 만듭니다.
이 프로젝트에서는 레시피당 1장의 사진만 추가되므로 [미디어(Media)]필드에서는 single media를 선택해야 합니다.
카테고리 필드는 아직 걱정하지 마십시오.
모든 필드를 추가한 후 Content Type을 저장해야 합니다.
필드를 추가할 때 이걸 따라해서 앞을 대문자로 주지 말자. 그렇게 되면 아래와 같이 Photo...같은 접근을 하게 된다. 실제로 나중에 주어진 코드들은 앞글자가 소문자로 되어 있다.
이제 category 컬렉션 타입을 만들겠습니다. 레시피를 작성할 때와 같은 프로세스를 사용합니다. COLLECTION TYPES에서 Create new collection type을 클릭합니다. 이 필드는 "category"로 명명되며,
category라는 이름의 Text 필드와 이 필드와 recipe 유형 간의 relation로 구성됩니다.
이전과 동일한 방법으로 Text 필드를 만든 다음 relation를 만듭니다. SQL 데이터베이스의 외부 키와 같이 두 타입이 서로 연결됩니다. 아래와 같은 모달 팝업이 표시됩니다.
왼쪽 박스에 카테고리, 오른쪽에 Recipe을 확인하세요. 둘 사이의 적절한 관계를 선택합니다.
각 카테고리는 다양한 레시피를 적용할 수 있습니다. 그리고 당신은 많은 레시피 관계들에 속하는 카테고리를 선택할 수 있을 것입니다. 그런 다음 finish와 save합니다. 컬렉션 타입들은 이제 완성입니다.
여기서, 당신은 프론트엔드에 보여줄 수 있도록 몇몇 레시피, 카테고리를 생성해야 합니다. Content Manager > COLLECTION TYPES > category 로 가서 Add new entry 를 클릭하세요.
category 필드의 category 이름을 적고 Save합니다, 그리고 Publish를 누르세요. 당신이 원하는 카테고리 수만큼 수행하세요.
레시피도 Content Manager> COLLECTION TYPES> recipe 로 이동하여 버튼을 클릭하여 Add new entry 를 클릭하세요.
그리고 recipe name, ingredients, instructions의 필드를 채우세요.
원하는 경우 photo을 추가하고 오른쪽 드롭다운 메뉴에서 카테고리를 선택합니다.
Text 필드 또는 Rich Text 필드를 비워두면 나중에 프런트엔드에 오류가 발생하므로 해당 필드에 데이터를 추가해야 합니다.
Collection type을 만들고 일부 데이터를 입력한 후에는 권한을 설정해야 합니다. 이것에 의해, 프런트엔드에서 데이터에 확실히 도달할 수 있게 됩니다.
General> Settings> USER & PERMISS PLUGIN> Roles> Public 으로 이동합니다.
각 컨텐츠 타입에 대해 findfindone 체크박스를 켭니다.
Postman을 사용하여 컬렉션에 액세스하여 이 기능이 제대로 작동하는지 확인할 수 있습니다.
GET 요청을 localhost: 1337/api/recipes로 보내고 다른 요청을 localhost: 1337/api/categories로 보냅니다.
둘 다에 대한 응답으로 JSON 개체를 가져와야 합니다

Designing the Recipe Book

이제 백엔드가 실행되었고, Next.js에서 레시피 북을 작성할 차례입니다.
Next.js 앱을 아직 실행하지 않은 경우 (Strapi는 실행하면서) frontend 폴더로 이동하여 npm run dev 명령을 실행합니다.
Next.js 앱이 localhost:3000에서 열립니다.
당신의 기본 레시피 북은 모든 recipe들과 연결된 이름과 사진으로 구성될 것입니다.
이를 위해 재사용 가능한 Recipe 카드 컴포넌트를 만듭니다.
이 컴포넌트는 index.js에서 사용되며 각 레시피가 표시됩니다.
그런 다음 동적 라우팅을 사용하여 각 레시피를 보여주는 페이지를 만듭니다.
시작하려면 pages/index.jsHome 컴포넌트 아래에 비동기 getServerSideProps 함수를 작성하여 백엔드에서 레시피를 가져옵니다.
함수는 다음과 같습니다.
export async function getServerSideProps() { const recipeRes = await fetch('http://localhost:1337/api/recipes?populate=*'); const categoryRes = await fetch('http://localhost:1337/api/categories'); const recipes = await recipeRes.json(); const categories = await categoryRes.json(); return { props: { recipes, categories, }, }; }
TypeScript
복사
recipes 엔드포인트에서 사용되는 populate 파라미터에 주목해 주십시오. 기본적으로는 엔트리를 가져올 때 관계가 입력되지 않으므로 엔트리를 반환하도록 지정해야 합니다. 이를 수행하려면 URL에서 원하는 관계를 지정하거나 와일드카드(*)를 사용하여 위와 같이 모든 관계를 채웁니다. 이렇게 하면 각 레시피와 관련된 카테고리에 프런트엔드로 접근할 수 있습니다.
다음으로 레시피 카드 컴포넌트용 components 폴더와 그 안에 파일을 만듭니다. 이 컴포넌트는 recipe object를 인수로 사용하여 Recipe name, image 및 category를 표시합니다.
const Recipe = ({recipe}) => { return ( <div className='recipe-card'> <div className="recipe-name"><h3>{recipe.attributes.name}</h3></div> <img src={`http://localhost:1337${recipe.attributes.photo.data.attributes.formats.thumbnail.url}`} alt={recipe.name} className="recipe-image" /> <span className='category'>{recipe.attributes.category.data.attributes.category}</span> </div> ) } export default Recipe;
TypeScript
복사
이것으로 레시피 카드 컴포넌트를 만들었습니다.
pages/index.js로 돌아가서 Home 컴포넌트의 내용을 삭제합니다.
백엔드에서 검색해온 Recipe 오브젝트를 매핑하여 각 Recipe 컴포넌트를 렌더링하는 다음과 같이 바꿉니다.
export default function Home({ recipes }) { return ( <div> <Head> <title>Strapi Recipe Book</title> </Head> <div id="recipe-container"> {recipes.data.map((recipe) => <Recipe recipe={recipe} / key={recipe.data.id}>)} </div> </div> ); }
TypeScript
복사
You can do some basic styling here to make your cards look nicer. The focus of this tutorial isn’t the CSS, but you can get the result below using basic CSS with CSS Grid.

category : { data : null } 이 나온다. category로 가서 직접 릴레이션에 추가해준다.

Creating Recipe Pages Using Dynamic Routes

지금은 레시피의 이름과 사진은 볼 수 있지만 전체 레시피는 볼 수 없습니다.
Next.js에서 동적 라우트를 사용하여 ID를 기반으로 단일 레시피를 표시하는 페이지를 만들 수 있습니다.
[id
이를 수행하려면 pagesrecipe라는 폴더를 만들고 그 폴더에 [id].js라는 파일을 만듭니다.
이 파일의 코드는 다음과 같습니다.
import { marked } from 'marked'; import Link from 'next/link'; const fullRecipe = ({ recipe }) => { const getMarkdownText = (text) => { const formattedText = marked(text); return { __html: formattedText }; }; return ( <div className="full-recipe"> <h1>{recipe.attributes.name}</h1> <img src={`http://localhost:1337${recipe.attributes.photo.data.attributes.formats.medium.url}`} alt={recipe.attributes.name} width={400} /> <h3>Ingredients</h3> <div dangerouslySetInnerHTML={getMarkdownText(recipe.attributes.ingredients)} ></div> <h3>Instructions</h3> <div dangerouslySetInnerHTML={getMarkdownText( recipe.attributes.instructions )} ></div> <Link href="/"> <button className="home">Home</button> </Link> </div> ); }; export default fullRecipe; export async function getStaticPaths() { const response = await fetch('http://localhost:1337/api/recipes?populate=*'); const recipes = await response.json(); return { paths: recipes.data.map((recipe) => ({ params: { id: recipe.id.toString(), }, })), fallback: false, }; } export async function getStaticProps({ params }) { const response = await fetch( `http://localhost:1337/api/recipes/${params.id}?populate=*` ); const recipe = await response.json(); return { props: { recipe: recipe.data }, revalidate: 1, }; }
TypeScript
복사
참고로 photo가 아니라 Photo로 들어온다. 필드를 만들 때 소문자로 만들어야되나보다.
여기에는 많은 것이 포함되어 있으므로 먼저 getStaticPaths 함수에 초점을 맞추십시오.
이 함수는 build time에 호출되고 paths를 pre-render합니다. 이 경우 레시피 ID를 기반으로 경로를 사전 렌더링합니다(이것이 올바르게 작동하려면 ID를 문자열로 변환해야 합니다).
다음으로 getStaticProps 함수를 살펴보겠습니다. 이것은 기본적으로 index.js에서 작성한 함수와 동일하지만 여기서는 URL의 ID를 기반으로 단일 레시피를 가져옵니다.
예를 들어 localhost:3000/recipe/1은 ID 번호가 1인 레시피를 가져옵니다.
마지막으로 fullRecipe 컴포넌트를 확인합니다. 그러면 소품에서 레시피 오브젝트를 가져와 모든 필드가 표시됩니다.
Marked라는 라이브러리가 여기에 Import되어 getMarkdownText이라는 작은 함수로 사용되고 있는 것을 알 수 있습니다. 표시할 필드 중 일부(Ingredients 및 Instructions)는 Rich text 타입이기 때문에 필요합니다.
Strapi의 리치 텍스트는 Markdown을 사용하지만 레시피를 작성할 때 사용한 포맷은 프런트 엔드에 자동으로 올바르게 표시되지 않습니다.
Markdown 포맷을 HTML로 변환하려면 라이브러리(Marked)를 사용해야 합니다. Marked를 다운로드하여 설치하려면 npm install marked를 실행하십시오.
React에서 변환된 Markdown을 표시하려면 innerHTML를 대체하는 React의 dangerouslySetInnerHTML를 사용해야 합니다.
모든 레시피 페이지가 설정되면 레시피 컴포넌트로 돌아가서 링크를 추가합니다.
아래 코드에서 레시피 컴포넌트 안에 완전한 레시피로 연결되는 링크가 있음을 알 수 있습니다.
const Recipe = ({recipe}) => { return ( <div className='recipe-card'> <div className="recipe-name"> <a href={`http://localhost:3000/recipe/${recipe.id}`}> <h3>{recipe.attributes.name}</h3> </a> </div> <img src={`http://localhost:1337${recipe.attributes.photo.data.attributes.formats.thumbnail.url}`} alt={recipe.attributes.name} className="recipe-image" /> <span className='category'>{recipe.attributes.category.data.attributes.category}</span> </div> ) } export default Recipe;
TypeScript
복사

Conclusion

Strapi와 Next.js는 강력한 조합이고 전통 CMS의 대안이다. 컨텐츠 관리 및 표시 방식을 더 잘 제어하고 싶다면