React

strapi-api-creation-quick-guide

Asset Type
File Type
When to use
Reference
Created time
2022/03/13 15:27
Created by
Last edited time
2022/03/13 15:34

Strapi 데이터베이스 스키마 생성

Strapi는 데이터베이스 스키마를 생성하기 위한 쉬운 UI를 제공합니다. 구성을 변경하려면 프로젝트 파일을 편집해야 합니다. 예를 들어 env변수를 변경하려면 config/environments폴더를 편집해야 합니다.
Strapi에는 데이터베이스 스키마 생성을 위한 훌륭한 UI를 제공하는 콘텐츠 빌더 플러그인이 포함되어 있습니다. 플러그인은 데이터베이스와 독립적이며 SQL 및 NoSQL 데이터베이스에서 동일한 스키마를 사용할 수 있습니다.
데모 웹사이트에는 블로그 컬렉션 유형과 댓글 컬렉션 유형이 있습니다. 블로그는 대부분의 콘텐츠를 저장하고 댓글 모음은 블로그에 대한 사용자 정보와 댓글을 저장합니다.

컬렉션 만들기

에서 관리자로 로그인하여 시작하십시오 http://localhost:1337/admin사이드바에서 Content-type builder 페이지를 엽니다 .

Documentation plugin

Swagger

Setting up Strapi’s JWT-based authentication

Strapi의 JWT 기반 인증 설정

인증은 모든 애플리케이션의 중요한 요소입니다. Strapi는 JWT 기반 인증을 즉시 사용할 수 있습니다.
기본 키는 JWT 서명에 사용됩니다. 서명 키는 구성 파일에서 변경할 수 있습니다 /extensions/users-permissions/config/jwt.json. 사용자 가입 및 로그인을 위한 API는 이미 플랫폼에 구워져 있습니다.
{ "jwtSecret": "f1b4e23e-480b-4e58-923e-b759a593c2e0" }
JSON
복사
우리는 local인증을 위해 공급자를 사용할 것입니다. 이 비밀번호와 이메일/사용자 이름은 사용자를 인증하는 데 사용됩니다. 사이드바에서 "문서"를 클릭하면 Swagger API 문서를 볼 수 있는 옵션이 제공됩니다.

Doc settings를 해야하는데 npm i로 설치해도 안보이는 상황

npm run develop으로 설치한 다음 새로고침하면 보임

근데 UserPermissions가 안 보인다. config/plugins.js

12555
issues
문서 열기 를 클릭 하여 Swagger API 문서를 보십시오. 사용자 권한 – 사용자 로 이동 하여 API에 액세스하여 사용자 및 로그인 사용자를 생성합니다.
우리는 /auth/local및 를 사용할 것 /auth/local/register입니다.

인증 역할 및 권한

Strapi에는 기본적으로 콘텐츠에 대한 액세스를 제어하는 데 사용되는 두 가지 역할(공개 및 인증)이 있습니다. 공개 역할은 인증되지 않은 사용자를 위한 것이고 인증된 역할은 인증된 사용자를 위한 것입니다.
이러한 역할은 인증 상태에 따라 사용자에게 자동으로 할당됩니다. "공개" 사용자는 블로그와 댓글을 읽을 수 있고 "인증된" 사용자는 블로그에 댓글을 달고 댓글을 수정할 수 있습니다. 역할은 역할 및 권한 섹션 에서 편집할 수 있습니다 .
이 화면에서 공개 역할을 편집하여 블로그 및 댓글에 대한 액세스를 허용할 수 있습니다.

댓글 추가

이제 데모 웹사이트에 댓글을 추가해 보겠습니다. 댓글을 추가하려면 사용자 인증이 필요합니다. 
Commentcollection에 대한 컨트롤러를 사용자 지정하여 Commentcollection에 대한 쓰기 액세스를 제어해야 합니다 . 모든 컬렉션의 컨트롤러는 api 폴더에 있습니다. 컨트롤러를 변경하려면
comment collectionCommentapiapi/comment/controllers/comment.js 를 편집 하십시오.
strapi-utils컨트롤러를 편집 하려면 설치해야 합니다 .
npm i strapi-utils // file: api/comment/controllers/comment.js const { sanitizeEntity } = require('strapi-utils'); module.exports = { // this method is called when api to create comment is called async create(ctx) { // add user from the request and add it to the body of request ctx.request.body.user = ctx.state.user.id; // call the function to creating comment with data let entity = await strapi.services.comment.create(ctx.request.body); // return data for api after removing field which are not exported return sanitizeEntity(entity, { model: strapi.models.comment }); }, async update(ctx) { // get the id of comment which is updated const { id } = ctx.params; // finding the comment for user and id const [comment] = await strapi.services.comment.find({ id: ctx.params.id, 'user.id': ctx.state.user.id, }); // comment does not exist send error if (!comment) { return ctx.unauthorized(`You can't update this entry`); } // update the comment let entity = await strapi.services.comment.update({ id }, ctx.request.body); // return data for api after removing field which are not exported return sanitizeEntity(entity, { model: strapi.models.comment }); }, async delete(ctx) { // get the id of comment which is updated const { id } = ctx.params; // finding the comment for user and id const [comment] = await strapi.services.comment.find({ id: ctx.params.id, 'user.id': ctx.state.user.id, }); // comment does not exist send error if (!comment) { return ctx.unauthorized(`You can't update this entry`); } // delete the comment let entity = await strapi.services.comment.delete({ id }); // return data for api after removing field which are not exported return sanitizeEntity(entity, { model: strapi.models.comment }); }, };
TypeScript
복사
여기에서는 요청 본문에 사용자 데이터를 추가할 수 있도록 Strapi에서 제공하는 기능 위에 레이어를 추가하기만 하면 됩니다. 나머지는 스트라피가 처리합니다.
이제 사용자가 주석을 작성, 편집 및 삭제할 수 있도록 인증 된 사용자 역할을 변경해야 합니다.

Gatsby 프론트엔드 구현

프론트엔드에서는 Gatsby 를 사용할 것 입니다. 를 사용하여 새 Gatsby 프로젝트를 만듭니다 gatsby new frontend. 우리 프로젝트의 파일 구조는 다음과 같습니다.
src/ ├── 구성 요소 │ ├── 카드.js │ └── 대화.js ├── 이미지 └── 페이지 ├──404.js ├── 블로그.js └── 인덱스.js
Plain Text
복사

프론트엔드 구성요소

프론트엔드에서 사용할 구성 요소를 살펴보겠습니다.
card.js
소품으로 제공된 정보를 표시하는 간단한 카드 구성 요소가 포함되어 있습니다.
dialog.js
로그인 및 가입을 위한 대화 상자 포함
blog.js
블로그 및 댓글을 표시하는 데 사용됩니다.
index.js
블로그 목록을 표시하는 홈페이지입니다.
404.js
URL을 찾을 수 없는 경우 오류를 표시합니다.

홈페이지 디자인하기

API에 GET 요청을 /blogs보내 모든 블로그 게시물을 가져옵니다. 이렇게 하면 블로그 게시물 목록이 매핑되고 각 블로그 게시물에 대한 카드 구성 요소가 표시됩니다. 여기에는 로그인/등록 대화 상자를 표시하기 위한 코드도 포함되어 있습니다.
사용자가 카드를 클릭하면 /blog페이지로 이동합니다.
import React, { useState } from 'react'; import { makeStyles } from '@material-ui/core/styles'; import Grid from '@material-ui/core/Grid'; import Typography from '@material-ui/core/Typography'; import Card from "../components/card"; import Dialog from "../components/dialog" import { Button } from '@material-ui/core'; const useStyles = makeStyles((theme) => ({ root: { flexGrow: 1, textAlign: "center" }, paper: { height: 500, width: 400, }, control: { padding: theme.spacing(2), }, })); export default function () { const classes = useStyles(); const [blogs, setBlogs] = useState([]) const [open, setOpen] = useState(false) const [login, setLogin] = useState(false) // fetch all blogs React.useEffect(() => { fetch("http://localhost:1337/blogs").then(res => res.json()).then(val => setBlogs(val)) }, []) return ( <> {/*dialog for authentication */} <Dialog open={open} setOpen={setOpen} login={login} /> <Grid container className={classes.root} spacing={2}> <Grid item xs={12}> <Grid container justify="center"> <Grid item xs={10}> <Typography variant="h3" component="h2" gutterBottom gutterLeft>Blogs</Typography> </Grid> {/*check if token is present or not */} { !localStorage.getItem("token") ? [<Grid item xs={1}> <Button onClick={() => { setOpen(true); setLogin(true) }}>Login</Button> </Grid>, <Grid item xs={1}> <Button onClick={() => { setOpen(true); setLogin(false) }}> Register</Button> </Grid>] : "" } </Grid> </Grid> <Grid item xs={12}> <Grid container justify="center" spacing={10}> {/*map through list of blog and create list of cards */} {blogs.map((value) => ( <Grid key={value} item> <Card value={value} /> </Grid> ))} </Grid> </Grid> </Grid> </> ); }
TypeScript
복사

카드 구성 요소 디자인

import React from 'react'; import { makeStyles } from '@material-ui/core/styles'; import Card from '@material-ui/core/Card'; import CardHeader from '@material-ui/core/CardHeader'; import CardMedia from '@material-ui/core/CardMedia'; import CardContent from '@material-ui/core/CardContent'; import CardActions from '@material-ui/core/CardActions'; import Collapse from '@material-ui/core/Collapse'; import Typography from '@material-ui/core/Typography'; import { red } from '@material-ui/core/colors'; import { Link } from 'gatsby'; const useStyles = makeStyles((theme) => ({ root: { maxWidth: 345, }, media: { height: 0, paddingTop: '56.25%', // 16:9 }, expand: { transform: 'rotate(0deg)', marginLeft: 'auto', transition: theme.transitions.create('transform', { duration: theme.transitions.duration.shortest, }), }, expandOpen: { transform: 'rotate(180deg)', }, avatar: { backgroundColor: red[500], }, })); export default function NewCard({ value }) { const classes = useStyles(); return ( <Link to={`/blog`} state={{ value }}> <Card className={classes.root}> <CardHeader subheader={`Published On ${new Date(value.created_at).toLocaleDateString("in")}`} /> <CardMedia className={classes.media} image={"http://localhost:1337" + value.image.url} /> <CardContent> <Typography variant="body2" color="textSecondary" component="p"> {value.title} </Typography> </CardContent> </Card></Link> ); }
TypeScript
복사

블로그 페이지 디자인하기

페이지에 전달된 위치 소품에서 블로그의 세부 정보를 가져오고 에 대한 GET 요청을 사용하여 블로그에 대한 댓글을 가져옵니다 /comments?blog={{blog-id}}blog-id현재 블로그의 ID입니다.
/comments그런 다음 헤더에 JWT 토큰 을 사용하여 POST 요청을 수행합니다. 이 토큰은 로컬 저장소에 저장됩니다.
import React, { useState, useEffect } from 'react'; import { makeStyles } from '@material-ui/core/styles'; import Grid from '@material-ui/core/Grid'; import Typography from '@material-ui/core/Typography'; import List from '@material-ui/core/List'; import ListItem from '@material-ui/core/ListItem'; import ListItemIcon from '@material-ui/core/ListItemIcon'; import ListItemText from '@material-ui/core/ListItemText'; import Avatar from '@material-ui/core/Avatar'; import { TextareaAutosize } from '@material-ui/core'; import Button from "@material-ui/core/Button" const useStyles = makeStyles((theme) => ({ root: { flexGrow: 1, textAlign: "center" }, paper: { height: 500, width: 400, }, control: { padding: theme.spacing(2), }, content: { margin: "100px" } })); export default function ({ location }) { const classes = useStyles(); const [comments, setComments] = useState([]) const [content, setContent] = useState("") useEffect(() => { fetch(`http://localhost:1337/comments?blog=${location.state.value.id}`).then(res => res.json()).then(val => setComments(val)) }, []) const submitComment = () => { fetch("http://localhost:1337/comments", { method: "post", headers: { "content-type": "application/json", authorization: `Bearer ${localStorage.getItem("token")}` }, body: JSON.stringify({ content, blog: location.state.value.id }) }).then(() => fetch(`http://localhost:1337/comments?blog=${location.state.value.id}`).then(res => res.json()).then(val => setComments(val))) } return ( <> <Grid container className={classes.root} spacing={2}> <Grid item xs={12}> <Grid container justify="center"> <Grid item xs={10}> <Typography variant="h3" component="h2" gutterBottom gutterLeft>{location.state.value.title}</Typography> </Grid> </Grid> </Grid> <Grid container justify="center"> <img src={"http://localhost:1337" + location.state.value.image.url}></img> </Grid> <Grid item xs={12} className={classes.content}> <Grid container justify="center" spacing={10}> {location.state.value.content} </Grid> </Grid> <Typography variant="h4" component="h2" gutterBottom gutterLeft>Comments</Typography> <Grid item xs={12}><TextareaAutosize minLength={10} rowsMin={10} style={{ width: "100%" }} value={content} onChange={(e) => setContent(e.target.value)} /></Grid> <Grid item xs={12}><Button onClick={submitComment}>Submit comment</Button></Grid> <Grid item xs={12}> <Grid container justify="left"> <List> { comments.map((val) => <ListItem> <ListItemIcon><Avatar>{val.user.username[0]}</Avatar></ListItemIcon> <ListItemText primary={`${val.user.username} said `} /> <ListItemText secondary={": " + val.content} /> </ListItem>) } </List> </Grid> </Grid> </Grid> </> ); }
TypeScript
복사

RESTful 로그인 대화 상자 구성 요소 추가

다음은 사용자 에게 로그인하라는 메시지 가 표시될 때 대화 상자 구성 요소의 모양 입니다 .
/auth/local/register사용자 이름, 이메일 및 비밀번호를 사용하여 사용자 등록을 위해 POST 요청을 합니다. 등록이 성공하면 JWT 토큰이 반환되고 나중에 사용할 수 있도록 로컬 저장소에 저장됩니다.
로그인을 위해 및 /auth/local2개의 필드 를 사용 하여 에 대한 POST 요청을 수행합니다 . 이메일 또는 사용자 이름이 될 수 있습니다.identifierpasswordidentifier

Register 요청시 api/를 넣어서 해야함 url확인은 브라우저로 → 허용된 메소드가 아니라는 405에러는 url이 잘못되서

그다음 에러는 400 에러

에러 해결하면 200 응답

import React, { useState } from 'react'; import Button from '@material-ui/core/Button'; import TextField from '@material-ui/core/TextField'; import Dialog from '@material-ui/core/Dialog'; import DialogActions from '@material-ui/core/DialogActions'; import DialogContent from '@material-ui/core/DialogContent'; import DialogContentText from '@material-ui/core/DialogContentText'; import DialogTitle from '@material-ui/core/DialogTitle'; export default function FormDialog({ open, setOpen, login }) { const [pass, setPass] = useState("") const [email, setEmail] = useState("") const [user, setUser] = useState("") const handleSubmit = () => { if (!login) fetch("http://localhost:1337/auth/local/register", { method: "post", headers: { "content-type": "application/json" }, body: JSON.stringify({ password: pass, email, username: user }) }).then((res) => res.json()) .then(res => localStorage.setItem("token", res.jwt)).finally(() => setOpen(false)) else fetch("http://localhost:1337/auth/local", { method: "post", headers: { "content-type": "application/json" }, body: JSON.stringify({ password: pass, identifier: user || email }) }).then((res) => res.json()) .then(res => localStorage.setItem("token", res.jwt)).finally(() => setOpen(false)) }; const handleClose = () => { setOpen(false); }; return ( <div> <Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title"> <DialogTitle id="form-dialog-title">{login ? "Login" : "Register"}</DialogTitle> <DialogContent> <DialogContentText> Please provide details </DialogContentText> <TextField autoFocus margin="dense" id="email" label="Email Address" type="email" fullWidth value={email} onChange={(e) => { setEmail(e.target.value) }} /> <TextField autoFocus margin="dense" id="username" label="Username" type="email" fullWidth value={user} onChange={(e) => { setUser(e.target.value) }} /> <TextField autoFocus margin="dense" id="password" label="Password" type="password" fullWidth value={pass} onChange={(e) => { setPass(e.target.value) }} /> </DialogContent> <DialogActions> <Button onClick={handleClose} color="primary"> Cancel </Button> <Button onClick={handleSubmit} color="primary"> Submit </Button> </DialogActions> </Dialog> </div> ); }
TypeScript
복사

SQLite에서 PostgreSQL로 전환

Strapi는 NoSQL과 SQL 데이터베이스를 모두 지원합니다. 데이터베이스 변경은 구성 폴더에서 env 변수를 변경하는 것만큼 간단합니다.
기본적으로 Strapi는 로컬 테스트에 적합한 SQLite를 사용하지만 프로덕션에서는 PostgreSQL 또는 MySQL과 같은 프로덕션 준비 데이터베이스를 사용해야 합니다. 여기서는 PostgreSQL을 사용하겠습니다.
데이터베이스를 변경하려면 config/environments/production/database.json파일을 편집하십시오.
{ "defaultConnection": "default", "connections": { "default": { "connector": "bookshelf", "settings": { "client": "postgres", "host": "${process.env.DATABASE_HOST }", "port": "${process.env.DATABASE_PORT }", "database": "${process.env.DATABASE_NAME }", "username": "${process.env.DATABASE_USERNAME }", "password": "${process.env.DATABASE_PASSWORD }" }, "options": {} } } }
JSON
복사
이제 프로덕션 환경 변수에서 데이터베이스 자격 증명을 선택합니다.

결론

이제 Strapi에 대한 기본적인 이해와 추가 탐색을 위한 견고한 기초가 있어야 합니다. 관계가 있는 데이터베이스 스키마를 생성하고, 인증을 구현하고, 컨트롤러를 사용자 지정하고, 데이터를 필터링하는 방법을 시연했습니다.
Strapi는 백엔드 API를 만드는 데 적합합니다. 사용자 정의가 가능하며 광범위한 통합을 지원합니다. Strapi는 Nuxt, React, Angular — 모든 프론트엔드 프레임워크와 함께 사용할 수 있습니다.

Postgres는 적용하지 않음, login API는 callback으로 되어 잇음

reload()를 꺼놓고 콘솔찍어봄. username은 전달하지 않아도 되는지 확인

identifier에는 user || email로 되어 있음

userSlice와 useUser를 만듦

RTK는 redux-persist와 어떻게 사용하는가