Fanua를 사용하여 블로그 애플리케이션을 구축하는 방법, 다음 단계.js,GraphQL, 편집기.js
스택
동물군
Fauna은 전 세계적으로 분포되고 지연이 적은 데이터베이스로서 시종일관 일치성과 안전성을 유지하겠다고 약속한다.기존 데이터베이스와 달리 GraphQL 지원을 갖추고 있어 응용 프로그램이 API를 통해 데이터에 접근할 수 있도록 합니다. 기존 데이터베이스는 사용할 수 있기 전에 연결을 열어야 합니다.
모든 블로그 게시물을 Fanua에 저장합니다.그리고 Graphql을 사용하여 한 번에 하나의 댓글이나 전체 목록을 얻습니다!
다음.js
Next.js은 React에서 지원하는 강력한 프런트엔드 프레임워크입니다.정적 페이지와 동적 내용을 모두 지원합니다.Next는 동물군에 적합한 후보자이다.데이터베이스에 데이터를 저장할 수 있습니다.Fauna의Graphql 데이터 API를 사용하면 Google의 게시물 데이터를 조회하고 앞에 표시할 수 있습니다.
편집자js
텍스트 편집기는 브라우저의 정보를 편집하는 데 도움을 줄 수 있기 때문에 내용 작성자에게 없어서는 안 될 도구입니다.만약 당신이 텍스트 편집기를 만들어 본 적이 있다면, 이 작업이 얼마나 어려운지 분명히 알 수 있습니다.
저희 프로젝트에 대해 EditorJS을 사용했습니다. 간단하고 사용하기 쉬우며 깨끗한 JSON 출력을 가지고 플러그인을 지원하여 사용자가 그 기능을 더욱 확장할 수 있도록 합니다.
1단계 - 설정
동물대에 반드시 참가 신청을 해야 한다.그들은 동물과 함께 등록할 수 있는 아주 좋은 무료 층을 가지고 있다.100k 읽기 작업, 50k 쓰기 작업, 500k 컴퓨팅 작업, 5GB 스토리지로 구성됩니다.
데이터베이스 만들기
Create a database에서 왼쪽 보안 탭으로 이동한 다음 새 키를 클릭하여 관리자 토큰을 생성합니다.새 키의 이름을 지정하고 관리자 역할을 선택하십시오.Nex에서 사용할 안전한 위치에 토큰을 저장합니다.js 응용 프로그램.
모드 만들기
왼쪽 사이드바에서 GraphQL을 클릭하고 Import Schema 버튼을 클릭합니다.
우리의 모델은 아래와 같다
type Post {
content: String!
published: Boolean
slug: String!
}
type Query {
allPosts: [Post!]
findPostBySlug(slug: String!): Post
}
이 모드를 파일에 저장합니다. pop에서 파일을 선택해야 할 때 저장 모드의 위치를 선택하십시오.끝점 숙지
게시물 작성
이제 왼쪽 사이드바에 있는 GraphQL 부분으로 돌아가 GraphQL 운동장에서 다음과 같은 내용을 운행한다.
mutation CreatePost {
createPost( data:{
content: "Hello World"
slug: "hello-world"
published: true
}){
content
published
slug
}
}
결과는 아래와 같아야 한다왼쪽 열에서collections를 누르면
Post
이라는 집합을 볼 수 있습니다. 이것은 모드를 가져올 때 자동으로 만들어집니다.이 집합에서 GraphQL 놀이공원에서 방금 실행한 내용을 포함하는 문서를 볼 수 있을 것입니다.Slug으로 글 가져오기
GraphQL 부분에서 다음 조회를 실행합니다
query {
findPostBySlug(slug: "hello-world"){
content
slug
published
}
}
이 검색어는 slug 필터를 사용하여 특정한 블로그 글을 얻었습니다.모든 댓글을 다 가져오세요.
GraphQL 부분에서 다음 조회를 실행합니다
query {
allPosts {
data {
content
published
slug
}
}
}
이 검색은 모든 댓글을 가져오고 내용, 발표 상태, slug를 되돌려줍니다.2단계 - 다음 설정js 프로젝트
터미널을 열고 실행하려면:
npx create-next-app fauna-blog
프로젝트 디렉터리에 들어가서 의존항을 설치합니다cd fauna-blog
npm i @apollo/client apollo-cache-inmemory apollo-client apollo-link-http @bomdi/codebox @editorjs/checklist @editorjs/delimiter @editorjs/editorjs @editorjs/header @editorjs/inline-code @editorjs/list @editorjs/marker @editorjs/paragraph @headlessui/react @heroicons/react @tailwindcss/forms editorjs-react-renderer graphql graphql-tag
미풍
다음 단계에서 설정하는 방법은 TailwindCSS instructions을 참조하십시오.js 프로젝트.
GraphQL 클라이언트
Google은
@apollo/client
을 GraphQL 클라이언트로 사용하여 Fanura 포트에서post 데이터를 가져오고post를 만듭니다.프로젝트의 루트 디렉터리에
lib
이라는 디렉터리를 만들고 그 디렉터리에 apolloClient.js
이라는 파일을 만듭니다.import {
ApolloClient,
HttpLink,
ApolloLink,
InMemoryCache,
concat,
} from "@apollo/client";
const httpLink = new HttpLink({ uri: process.env.FAUNA_GRAPHQL_ENDPOINT });
const authMiddleware = new ApolloLink((operation, forward) => {
// add the authorization to the headers
operation.setContext(({ headers = {} }) => ({
headers: {
...headers,
authorization:
`Basic ${process.env.FAUNA_TOKEN}`,
},
}));
return forward(operation);
});
const apolloClient = new ApolloClient({
cache: new InMemoryCache(),
link: concat(authMiddleware, httpLink),
});
export default apolloClient;
프로젝트의 루트 디렉터리에 .env
이라는 파일을 만듭니다. 아래와 같습니다.FAUNA_GRAPHQL_ENDPOINT="https://graphql.fauna.com/graphql"
FAUNA_TOKEN="YOUR-TOKEN"
구성 요소
이 프로젝트에서, 우리는
Editor
에 사용할 구성 요소 하나만 있습니다.이 구성 요소 중ready
일 때 사용자가 changes
단추를 눌렀을 때 무엇을 하고 싶은지 정의합니다.마지막 단계는 사용자가 save 단추를 눌렀을 때, 블로그 글 내용을 저장하기 위해 결과를 Fanora Endpoint에 보내기를 원하기 때문에 우리에게 매우 중요하다.
import React from "react";
import { useEffect, useRef, useState } from "react";
import EditorJS from "@editorjs/editorjs";
import Header from "@editorjs/header";
import List from "@editorjs/list";
import Quote from "@editorjs/quote";
import Delimiter from "@editorjs/delimiter";
import InlineCode from "@editorjs/inline-code";
import Marker from "@editorjs/marker";
import Embed from "@editorjs/embed";
import Image from "@editorjs/image";
import Table from "@editorjs/table";
import Warning from "@editorjs/warning";
import Code from "@editorjs/code";
import Checklist from "@editorjs/checklist";
import LinkTool from "@editorjs/link";
import Raw from "@editorjs/raw";
import Paragraph from "@editorjs/paragraph";
import Codebox from "@bomdi/codebox";
import gql from "graphql-tag";
import apolloClient from "../lib/apolloClient";
export default function Editor() {
const editorRef = useRef(null);
const [editorData, setEditorData] = useState(null);
const initEditor = () => {
const editor = new EditorJS({
holderId: "editorjs",
tools: {
header: {
class: Header,
inlineToolbar: ["marker", "link"],
config: {
placeholder: 'Enter a header',
levels: [1, 2, 3, 4, 5, 6],
defaultLevel: 3
},
shortcut: "CMD+SHIFT+H",
},
image: Image,
code: Code,
paragraph: {
class: Paragraph,
inlineToolbar: true,
},
raw: Raw,
inlineCode: InlineCode,
list: {
class: List,
inlineToolbar: true,
shortcut: "CMD+SHIFT+L",
},
checklist: {
class: Checklist,
inlineToolbar: true,
},
quote: {
class: Quote,
inlineToolbar: true,
config: {
quotePlaceholder: "Enter a quote",
captionPlaceholder: "Quote's author",
},
shortcut: "CMD+SHIFT+O",
},
warning: Warning,
marker: {
class: Marker,
shortcut: "CMD+SHIFT+M",
},
delimiter: Delimiter,
inlineCode: {
class: InlineCode,
shortcut: "CMD+SHIFT+C",
},
linkTool: LinkTool,
embed: Embed,
codebox: Codebox,
table: {
class: Table,
inlineToolbar: true,
shortcut: "CMD+ALT+T",
},
},
// autofocus: true,
placeholder: "Write your story...",
data: {
blocks: [
{
type: "header",
data: {
text: "New blog post title here....",
level: 2,
},
},
{
type: "paragraph",
data: {
text: "Blog post introduction here....",
},
},
],
},
onReady: () => {
console.log("Editor.js is ready to work!");
editorRef.current = editor;
},
onChange: () => {
console.log("Content was changed");
},
onSave: () => {
console.log("Content was saved");
},
});
};
const handleSave = async () => {
// 1. GQL mutation to create a blog post in Fauna
const CREATE_POST = gql`
mutation CreatePost($content: String!, $slug: String!) {
createPost(data: {published: true, content: $content, slug: $slug}) {
content
slug
published
}
}
`;
// 2. Get the content from the editor
const outputData = await editorRef.current.save();
// 3. Get blog title to create a slug
for (let i = 0; i < outputData.blocks.length; i++) {
if (
outputData.blocks[i].type === "header" &&
outputData.blocks[i].data.level === 2
) {
var title = outputData.blocks[i].data.text;
break;
}
}
const slug = title.toLowerCase().replace(/ /g, "-").replace(/[^\w-]+/g, "");
//3. Pass the content to the mutation and create a new blog post
const { data } = await apolloClient.mutate({
mutation: CREATE_POST,
variables: {
content: JSON.stringify(outputData),
slug: slug,
},
});
};
useEffect(() => {
if(!editorRef.current) {
initEditor();
}
}, []);
return (
<div>
<div id="editorjs" />
<div className="flex justify-center -mt-30 mb-20">
<button
type="button"
onClick={handleSave}
className="inline-flex items-center px-12 py-3 border border-transparent text-base font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
Save
</button>
</div>
</div>
);
}
페이지
저희가 3페이지가 될 거예요.
save
은 사용자가 저희 프로젝트에 로그인할 때 모든 블로그 게시물을 표시하는 곳입니다.예컨대 https://fauna-blog-psi.vercel.appIndex.js
은 동적 페이지로 특정한 블로그 글의 내용을 나타낸다.예컨대 https://fauna-blog-psi.vercel.app/posts/hello-world[slug].js
은 EditorJS를 사용하여 새로운 블로그 글을 만들 수 있는 곳입니다.예컨대 https://fauna-blog-psi.vercel.app/posts/new색인 페이지
이 페이지에서는 Fanura API에서 모든 게시물을 가져와 서버측 도구로 페이지에 전달합니다.
new.js
함수에서GraphQL 함수를 찾을 수 있습니다.import gql from "graphql-tag";
import apolloClient from "../lib/apolloClient";
import Link from "next/link";
export default function Home(posts) {
let allPosts = [];
posts.posts.map((post) => {
const content = JSON.parse(post.content);
const published = post.published;
const slug = post.slug;
for (let i = 0; i < content.blocks.length; i++) {
if (
content.blocks[i].type === "header" &&
content.blocks[i].data.level === 2
) {
var title = content.blocks[i].data.text;
break;
}
}
for (let i = 0; i < content.blocks.length; i++) {
if (content.blocks[i].type === "paragraph") {
var description = content.blocks[i].data.text;
break;
}
}
title === undefined ? (title = "Without Title") : (title = title);
description === undefined ? (description = "Without Description") : (description = description);
allPosts.push({
title,
description,
published,
slug,
});
});
return (
<div className="bg-white pt-12 pb-20 px-4 sm:px-6 lg:pt-12 lg:pb-28 lg:px-8">
<div className="relative max-w-lg mx-auto divide-y-2 divide-gray-200 lg:max-w-7xl">
<div>
<h2 className="text-3xl tracking-tight font-extrabold text-gray-900 sm:text-4xl">
From the blog
</h2>
<p className="mt-3 text-xl text-gray-500 sm:mt-4">
Don't miss these awesome posts with some of the best tricks and
hacks you'll find on the Internet!
</p>
</div>
<div className="mt-12 grid gap-16 pt-12 lg:grid-cols-3 lg:gap-x-5 lg:gap-y-12">
{allPosts.map((post) => (
<div
key={post.title}
className="border border-blue-100 py-8 px-6 rounded-md"
>
<div>
<Link href={`/posts/${post.slug}`}>
<a className="inline-block">
<span className="text-blue-100 bg-blue-800 inline-flex items-center px-3 py-0.5 rounded-full text-sm font-medium">
Article
</span>
</a>
</Link>
</div>
<Link href={`/posts/${post.slug}`}>
<a className="block mt-4">
<p className="text-xl font-semibold text-gray-900">
{post.title}
</p>
<p className="mt-3 text-base text-gray-500">
{post.description}
</p>
</a>
</Link>
<div className="mt-6 flex items-center">
<div className="flex-shrink-0">
<Link href={`/posts/${post.slug}`}>
<a>
<span className="sr-only">Paul York</span>
<img
className="h-10 w-10 rounded-full"
src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
alt=""
/>
</a>
</Link>
</div>
<div className="ml-3">
<p className="text-sm font-medium text-gray-900">
<span>Paul York</span>
</p>
<div className="flex space-x-1 text-sm text-gray-500">
<time dateTime="Nov 10, 2021">Nov 10, 2021</time>
<span aria-hidden="true">·</span>
<span>3 mins read</span>
</div>
</div>
</div>
</div>
))}
</div>
</div>
</div>
);
}
export async function getServerSideProps (context) {
// 1. GQL Queries to get Posts data from Faust
const POSTS_QUERY = gql`
query {
allPosts {
data {
content
published
slug
}
}
}
`;
const { data } = await apolloClient.query({
query: POSTS_QUERY,
});
return {
props: {
posts: data.allPosts.data,
},
};
}
새것js
이 페이지에서는 EditorJS의 인스턴스를 가져오고 게시물을 만들기 위해 편집기의 출력을 Fauna API로 보냅니다.
NextJS dynamic import을 사용하여 EditorJS를 가져옵니다. EditJS는 SSR에 적용되지 않기 때문에 클라이언트가 실행된 후에 코드를 가져와야 합니다.
import dynamic from "next/dynamic";
const Editor = dynamic(
() => import("../../components/editor"),
{ ssr: false }
);
export default function CreatePost() {
return (
<>
<div className="min-h-full">
<div className="bg-gray-800 pb-32">
<header className="py-10">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<h1 className="text-3xl font-bold text-white">
Create a new post
</h1>
</div>
</header>
</div>
<main className="-mt-32">
<div className="max-w-7xl mx-auto pb-12 px-4 sm:px-6 lg:px-8">
{/* Replace with your content */}
<div className="bg-white rounded-lg shadow px-5 py-6 sm:px-6">
<div className="border-4 border-dashed border-gray-200 rounded-lg pt-10">
<Editor />
</div>
</div>
{/* /End replace */}
</div>
</main>
</div>
</>
);
}
콧물벌레.js
이 페이지에서 우리는 구체적인 블로그 글을 보여 주었다.Google은 쿼리에서 블로그 slug를 얻었고 Fauna API
getServerSideProps
쿼리를 사용하여 slug를 통해 게시물을 찾았습니다.그리고 우리는 블로그 데이터를 findPostBySlug
으로 전달할 것이다.이 페이지에서는 EditorJS 출력을 ServerSideProps
으로 보여 줍니다.import { useRouter } from "next/router";
import Output from "editorjs-react-renderer";
import gql from "graphql-tag";
import apolloClient from "../../lib/apolloClient";
import Link from "next/link";
export default function Post({ post }) {
const content = JSON.parse(post.content);
return (
<div className="min-h-full">
<div className="bg-gray-800 pb-32">
<header className="py-10">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<Link href="/">
<a className="text-3xl font-bold text-white">
Home
</a>
</Link>
</div>
</header>
</div>
<main className="-mt-32">
<div className="max-w-7xl mx-auto pb-12 px-4 sm:px-6 lg:px-8">
{/* Replace with your content */}
<div className="bg-white rounded-lg shadow px-5 py-6 sm:px-6">
<div className="border-4 border-dashed border-gray-200 rounded-lg py-10 px-32">
<Output data={content} />
</div>
</div>
{/* /End replace */}
</div>
</main>
</div>
);
}
export async function getServerSideProps(context) {
const { slug } = context.query;
const { data } = await apolloClient.query({
query: gql`
query Post($slug: String!) {
findPostBySlug(slug: $slug) {
content
published
slug
}
}
`,
variables: {
slug,
},
});
return {
props: {
post: data.findPostBySlug,
},
};
}
어떻게 일하는지 보여주세요.
결론
과거에는 응용 프로그램의 지속적인 데이터 층을 실현하기 위해 우리는 보통 새로운 서버를 시작하여 거기에 데이터베이스를 설치하고 모델을 만들고 데이터를 불러왔다. 우리의 응용 프로그램에서 클라이언트를 사용함으로써 우리는 CRUD를 조작할 수 있다.그러나 본고에서 보듯이 몇 분 안에 데이터베이스와 API를 만들어서 데이터를NextJS에서 사용할 수 있고 서버 설정, 데이터베이스 설정과 조작 비용을 걱정할 필요가 없다.
본고에서 구축한 좋은 예는Fauna와Nextjs를 사용하여 블로그 응용 프로그램 등 복잡한 시스템을 처음부터 개발함으로써 개발 시간을 단축하는 방법을 설명한다.
Reference
이 문제에 관하여(Fanua를 사용하여 블로그 애플리케이션을 구축하는 방법, 다음 단계.js,GraphQL, 편집기.js), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/corpcubite/how-to-build-a-blogging-application-using-fauna-nextjs-graphql-and-editorjs-1cbm텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)