Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Cover image for Understanding and using Relations in Strapi
Strapi profile imageShada
Shada forStrapi

Posted on • Originally published atstrapi.io

     

Understanding and using Relations in Strapi

In this article, we will look into the relational fields in Strapi to see how we can utilize them to establish relationships in our models.

What is Strapi?

Strapi is an open-source Node.js headless CMS(Content Management System) based on Node.js used to develop APIs(RESTful and GraphQL APIs) and build the APIs content. The APIs in Strapi are built in the form of collections or single types.

A collection in Strapi will create and expose the endpoints on all the HTTP verbs. For example, if we have a blog collection. Strapi will create the following endpoints based on the collection:

  • blogGET: This will get all the blog entries from the endpoint.
  • blogPOST: This will create a new blog post from the endpoint.
  • blog/:idGET: This will return the blog post with the id:id.
  • blog/:idDELETE: This will delete the blog post with the id:id from the endpoint.

Strapi creates all those APIs for us. We can then add content to the collection via the admin panel or the Strapi API.

Internally, Strapi is powered byKoajs, and its default database isSQLite, where it persists the content we add to the collections and single-types. We have learned a bit about Strapi. In the next sections, we will learn about relations in database models and establish the relations in Strapi collections.

Relations in Database Fields and Strapi

The database contains tables, columns, and records. Now, relationships can be defined in the database tables. In Strapi, we can use relations to create links between our Content Types. This relationship is like a pointer or reference. They point to data in a table that depicts what they contain.

There are types of relationships we can establish in Strapi:

  • One-to-one (1:1)
  • One-to-Many
  • Many-to-Many
  • One-Way
  • Many-way
  • Polymorphic

One-to-one (1:1)

In this one-to-one relationship, a column in a table points toonly one column in another table.

For example, in aStudent table, astudentId column can point to aStudentInfo table. A column in theStudentInfo table,studentId points back to theStudent table. So here, theStudent table is associated with one and only one record in theStudentInfo table. We can fetch a student's info from theStudent table, and we can fetch a student from theStudentInfo table. That's aone-to-one relationship.

One-to-Many

This relation involves a table pointing to several or many tables. A column in table A can point to several tables(B, C, D), these tables in turn point to table A. Also, each table (A, B, C, D) can hold one or more records of the column in table A.

For example, let's say we have aCompany table. This table holds the list of all the companies in a system. We can create anEmployee table to hold the name of an employee. Now, we can add acompanyId column to the Employee table, and thiscompanyId will point to theCompany table.

Now aCompany table can point to many employee records in theEmployee table. Also, each record in theEmployee table points back to a record in theCompany table. The relation here isone-to-many.

Many-to-Many

This relationship involves a column in a table pointing to many records in another table and a column in another table pointing to many records in the first table. For example, manydoctors can be associated with manyhospitals.

One-Way

This relationship involves a column pointing or linking to another column in a table. The thing here is that the other column does not point back to the "pointing" column. One-way relation is similar to One-to-One relation but differs because the column being "pointed" does not link back to the pointing column.

For example, in aUser table, AdetailsId column in theUser table can point to aDetails table. This means that the details of a user are in thedetailsId column in theUser table and the details are stored in theDetails table.

So we see that theUser table points to only one table, which is theDetails table. The relationship isone-way. There is no column in theDetails table that points back to theUser table.

Many-way

This relation involves a column in a table pointing to many records in another table. The records being pointed to does not point back or link back to the record.

For example, aUser table has a columncarId that points to aCar table. ThecarId can point to many records in theCar table but theCar record does not point back to theUser table, this relationship is amany-way relationship.

Polymorphic

This relationship involves a column in a table that can link to different columns in other tables. In a polymorphic relationship, a model/table can be associated with different models/tables.
In other relationships we have seen, it is mainly between a table and another table, not more than three tables are involved in the relationship. But in a polymorphic relationship, multiple tables are involved.

For example, aTire table holds can be linked and have links to aToyota table,Mercedes table, etc. So aToyota can relate to the sameTire as aMercedes.
We have seen all the relations we have. The below sections will explain and show how we can set the relations from both the Strapi admin UI and a Strapi project.

Where are relations set in Strapi?

Relationship links can be set in the Admin panel and manually from the generated Strapi project.

  1. Via Strapi Admin Panel

Relations can be set in Strapi's Collection types, Single types, and Components. The relation is set when adding fields to our Collection, Single collection, or Component type. Therelation field is selected:

Content Type builder modal showing field types

Another UI is displayed in the modal:

User relation field modal

This is where we set the relations between the current model we are creating and an existing model.

We have two big boxes in the above picture, the left box is the current model we are creating, and the right box is the model the current model will be having relations with. We can click on the dropdown icon to select the model we want to link relations within the right box.

Question content type relation modal

The smaller boxes with icons are the relations we can establish between the two models in the bigger boxes.

Let's look at the smaller boxes starting from the left.

  • Thefirst box represents thehas one relation.

one-way relation

It establishes aone-way relation between content types in Strapi.

  • Thesecond box ishas one and belongs to one.

one-to-one relation

It links two content types in aone-to-one way relationship.

  • Thethird box isbelongs to many.

one-to-may relation

It links two content types in aone-to-many relation. The content type in the left-bigger box will have a field that links to many records in the content type that is in the right-bigger box. The field in the content type in the right-bigger box will have a field that links to a single record in the left-content type.

  • Thefourth box ishas many.

many-to-one relation

This one links two content types in amany-to-one relation. Here, the content type at the left-bigger box has a field that links to many records to the content type at the right-bigger box. It is the reverse of thebelongs to many boxes.

  • Thefifth box ishas and belongs to many.

many-to-many relation

This box links two content types in amany-to-many relationship. Both content types in the bigger boxes will have a field that links many records to each other.

  • Thesixth box ishas many.

many-way relation

It links two content types in amany-way relationship. The field on the left content type links to many records in the right content type. The right content type does not link back to the left content type.

2. Via Strapi Project

Let's see how we set relations in our content types from our Strapi project. The content types in a Strapi project are stored in the./src/api/ folder in our Strapi project. The relations are set in the./src/api/[NAME]/content-types/[NAME]/schema.json file.

Fields are set inside theattributes section. To set a relation field we use some properties likemodel,collection, etc. Let's see how we set the relations for all the types of relations in Strapi.

One-to-One (1:1)

To set aone-to-one relation between two content types, we’ll create a new property in theattributes property. Let's say we want to set aone-to-one between aStudent model and aStudent-info model, we will open the./src/api/student/content-types/student/schema.json file and add the code:

{"kind":"collectionType","collectionName":"students","info":{"singularName":"student","pluralName":"students","displayName":"Student","description":""},"options":{"draftAndPublish":true},"pluginOptions":{},// The fields are configured here"attributes":{"name":{"type":"string"},"student_info":{//field name"type":"relation",// field type"relation":"oneToOne",// relation type"target":"api::student-info.student-info",// the target of the relation"inversedBy":"student"// more info here - https://docs.strapi.io/developer-docs/latest/development/backend-customization/models.html#relations}}}
Enter fullscreen modeExit fullscreen mode

The relation field isstudent_info. Themodel refers to the content type in Strapi the field is pointing to. It is set tostudent_info and so this property in theStudent content type points to thestudent_info content type.

We set thetype asrelation and therelation asoneToOne. All these state that theStudent model has and belongs to oneStudentInfo.

Let's see inside./src/api/student-info/content-types/student-info/schema.json file

{"kind":"collectionType","collectionName":"student_infos","info":{"singularName":"student-info","pluralName":"student-infos","displayName":"studentInfo"},"options":{"draftAndPublish":true},"pluginOptions":{},"attributes":{"bio":{"type":"text"},"student":{"type":"relation","relation":"oneToOne","target":"api::student.student","inversedBy":"student_info"}}}
Enter fullscreen modeExit fullscreen mode

Here, we have astudent property which points to thestudent collection type. Therelation set here is alsooneToOne

These two JSON configs of bothStudent andStudentInfo models establish a one-to-one relationship between them as you can see in the interface below. This is similar for all other relations.

Content Type builder modal showing field types

One-to-Many

Let's say we have two content types,Employee andCompany. TheCompany has manyEmployee records, and theEmployee record points back to aCompany record.
To establish this in the content types, we will go to their/schema.json files in our project and set relation fields.

For theCompany model, we want anemployees relation to point to manyEmployees. So we will do the below in the./src/api/company/content-types/company/schema.json file.

{..."attributes":{"name":{"type":"string"},"employees":{"type":"relation","relation":"oneToMany","target":"api::employee.employee","mappedBy":"company"}}}
Enter fullscreen modeExit fullscreen mode

Also, in./src/api/employee/content-types/employee/schema.json file:

{..."attributes":{"name":{"type":"string"},"company":{"type":"relation","relation":"manyToOne","target":"api::company.company","inversedBy":"employees"}}}
Enter fullscreen modeExit fullscreen mode

This sets a one-to-many relationship in theCompany model.

Many-to-Many

In setting amany-to-many relation from our Strapi project, we will set the relation field of both content types.
For example, doctors can work in many hospitals and many hospitals can have many doctors. In this case, ourDoctor model in./src/api/doctor/content-types/doctor/schema.json will be this:

{..."attributes":{"name":{"type":"string"},"hospitals":{"type":"relation","relation":"manyToMany","target":"api::hospital.hospital","inversedBy":"doctors"}}}
Enter fullscreen modeExit fullscreen mode

Thehospital relation field points to many hospitals.

TheHospital model will be this:
./src/api/hospital/content-types/hospital/schema.json:

{..."attributes":{"name":{"type":"string"},"doctors":{"type":"relation","relation":"manyToMany","target":"api::doctor.doctor","inversedBy":"hospitals"}}}
Enter fullscreen modeExit fullscreen mode

This effectively sets a many-to-many relation between the Doctor and Hospital models.

One-Way

To set this relation from our Strapi project between two models, we will define a relation field in one model's/schema.json file only. The other model will have no relation connecting to other model define in its/schema.json file.

For example, we have two modelsUser andDetail and they have one-way relation. To set this up. We set the below in theUser's model fileuser/models/user.settings.json file:

{..."attributes":{"name":{"type":"string"},"details":{"type":"relation","relation":"oneToOne","target":"api::detail.detail"}}}
Enter fullscreen modeExit fullscreen mode

There will be no relation setting in theDetail schema file that will point to theUser model. So in this way, we have set a one-way relation between theUser andDetail models in Strapi.

Many-Way

This is the same as the one-way relation but this one involves one model pointing to many records in another model, but this another model does not point back.
To set this manually in Strapi, we will set a relation field with thecollection property in one model but no relation definition in the other model.

For example, aUser has manyCars. The relation is many-way. A user can own many cars. The setting will be this for theUser:
user/models/user.settings.json:

{..."attributes":{"name":{"type":"string"},"cars":{"type":"relation","relation":"oneToMany","target":"api::car.car"}}}
Enter fullscreen modeExit fullscreen mode

Thecar relation has acollection property that is set tocar. This setting tells Strapi that thecars field in theUser model points to manyCar records.
We will not make a relation in theCar model that will point back to theUser model because this is a many-way relation.

We have learned all the relations in Strapi and also learned how to set them up both via the Strapi admin UI panel and from a Strapi project. Now, we show how to use some of the relations in Strapi to build a real-life app.

Setting up Strapi Project

We will create a QnA app just like Quora, and users can ask questions, answer questions, and comment on answers. We will build this app so as to demonstrate how we can use Strapi relations to link our models.

This project will be in two parts: the backend and the frontend. Of course, the backend will be built using Strapi, and the frontend will be built using Next.js.

We will create a central folder that will hold both backend and frontend projects:

mkdirrelations
Enter fullscreen modeExit fullscreen mode

We move into the folder:

cdrelations
Enter fullscreen modeExit fullscreen mode

Create the Strapi project:

    yarn create strapi-app qa-app--quickstart
Enter fullscreen modeExit fullscreen mode

The above command will create a Strapi project inqa-app folder inside therelations folder.

To start the project, run:

    yarn develop
Enter fullscreen modeExit fullscreen mode

Strapi will serve the project onlocalhost:1337. It will launch the Strapi admin UI panel onlocalhost:1337/admin.

Strapi admin sign up page

Fill in your details and click on theLET'S START button. We will begin to build our collections but first, let's draw our models.

Models

We will have three models for our QnA app. We will haveQuestion,Answer andComment.
OurQuestion model will be this:

Question{qTextuser}
Enter fullscreen modeExit fullscreen mode
  • qText: This will hold the question.
  • user: This holds the name of the user.

Strapi Question content type

TheAnswer model will be this:

Answer{aTextquestionuser}
Enter fullscreen modeExit fullscreen mode
  • aText: This holds the answer text.
  • question: This holds the reference to the question.
  • user: The user that answered.

Answer content type

TheComment model will look like this:

Comment{cTextansweruser}
Enter fullscreen modeExit fullscreen mode
  • cText: This will hold the comment text on the answer.
  • answer: This is the reference to the answer.
  • user: The user that commented.

Comment content type

We have seen how our collection will look like, now let's build our collections. These models have relationships that connect them. Let's see them below.

One-to-Many

TheQuestion model and theAnswer model have a one-to-many relationship. A Question will have many Answers. Now, we will build aQuestion collection in Strapi, and also we will create theAnswer collection and there we will establish the relation between them. Now, on thehttp://localhost:1337/admin/ page click on theCreate First Content Type button, a modal will appear.
We will create theQuestion collection.

  • Typequestion in theDisplay name field.
  • Click on the text field.
  • TypeqText in theName field.
  • SelectLong Text in the below radio button.

Question model - qText field

  • Click on+ Add another field.
  • Selecttext.
  • Type inuser.

Question model - user field

  • Click onFinish.
  • Next, click on theSave button on the top-right of the page.

Next, we will create theAnswer collection

  • Click on the+ Create new collection type link, a modal will show up, type inanswer. Click on the+ Add another field button.
  • Selecttext and type inuser.

Answer model - aText field

  • Selectrelation field.
  • On the right box, press on the dropdown element and selectQuestion.
  • Click on the fourth small box, counting from left. The box establishes aone-to-many relationship between theQuestion collection and theAnswer collection.

Answer model - question one-to-many relation field

  • Click on theFinish button.
  • Next, click on theSave button on the top-right of the page.

One-to-One

TheComment model and theAnswer model have a one-to-one relationship. A comment has one answer.
We will create the Comment collection.

  • Click on the+ Create new collection type link, a modal will show up, type incomment.

Comment content type display name

  • Click on the+ Add another field button.
  • Selecttext field.
  • Type incText and click on the+ Add another field button.

Comment model - cText field

  • Selectrelation field.
  • On the big box on the right, click on the dropdown element and selectAnswer.
  • Select the first small box, counting from the left. This box establishes theone-to-one relationship between theComment and theAnswer but not fromAnswer to comment. So, thecomments field will not appear on theAnswer response.

Comment relation field relations

  • Click on theFinish button.
  • Next, click on theSave button on the top-right of the page.

We are done building our collections and establishing their relationships. Now, let's build the front end.

Before we start building the frontend, we have set the permissions for aPublic unauthenticated user so that our Strapi API can return data from routes without authentication.

NOTE: You’d typically need authentication in your application, especially when dealing withcreate,delete andupdate endpoints

Enable all permissions for answer collection type

Building the QnA App

Our app will have two pages: the index and the question view page.

  • / index: This page will display all questions in the app.
  • /questions/:id: This page is a dynamic page. It will display the details of a specific question. The details displayed are the answers to the question and the comments are replies to the answers.

Our app will look like this:

Viewing all questions

Viewing all questions

Adding new question

Adding new question

Answering a question

Answering a question

Commenting on an answer

Commenting on an answer

Deleting a question

Deleting a question

Viewing all answers and comments

Viewing all answers and comments

Viewing all answers and comments

We will start by scaffolding a Nextjs app. We will scaffold the Nextjs app inside therelations folder, so run the below command:

    yarn create next-app qa-front
Enter fullscreen modeExit fullscreen mode

Now, we move into the directory:

cdqa-front
Enter fullscreen modeExit fullscreen mode

We will need the following dependencies:

  • axios: We will need this for making HTTP calls to our Strapi collection endpoints.
  • quill: An editor we will use for answering questions in our app.

We will install axios:

    yarn add axios
Enter fullscreen modeExit fullscreen mode

We install and use quill via CDN. Open the_app.js file and add the following code to it:

import"../styles/globals.css";importHeadfrom"next/head";functionMyApp({Component,pageProps}){return(<><Head><scriptsrc="https://cdn.quilljs.com/1.3.6/quill.min.js"></script><linkhref="https://cdn.quilljs.com/1.3.6/quill.snow.css"rel="stylesheet"/><linkhref="https://cdn.quilljs.com/1.3.6/quill.bubble.css"rel="stylesheet"/></Head><Component{...pageProps}/></>);}exportdefaultMyApp;
Enter fullscreen modeExit fullscreen mode

We added the CDN scripts and style files of thequill editor to theHead tags of theMyApp component.

Creating Pages

First, we will create aHeader component, this component will render our header so it appears in our app.

Run the below command to generate theHeader files:

mkdircomponents components/Headertouchcomponents/Header/index.js components/Header/Header.module.css
Enter fullscreen modeExit fullscreen mode

Now, we open theHeader/index.js and paste the below code to it:

import{header,headerName}from"./Header.module.css";exportdefaultfunctionHeader(){return(<sectionclassName={header}><divclassName={headerName}>Q/AApp</div></section>);}
Enter fullscreen modeExit fullscreen mode

This component just renders the textQ/A App in the header section of our app. Now, to make the component appear application-wide in our app we will go the theMyApp component in_app.js file and render the component.

import"../styles/globals.css";importHeaderfrom"../components/Header";importHeadfrom"next/head";functionMyApp({Component,pageProps}){return(<><Head>...</Head><Header/><Component{...pageProps}/></>);}exportdefaultMyApp;
Enter fullscreen modeExit fullscreen mode

With this, ourHeader component will be rendered on all pages in our application.
Let's create our page components.

Theindex.js page will be loaded when the index route/ is navigated to.
So, open the index.js file and paste the below code to it:

importHeadfrom"next/head";importstylesfrom"../styles/Home.module.css";importQuestionCardfrom"../components/QuestionCard";import{useEffect,useState}from"react";importaxiosfrom"axios";importAddQuestionDialogfrom"../components/AddQuestionDialog";exportconstgetServerSideProps=async()=>{const{data}=awaitaxios.get("http://localhost:1337/api/questions?populate=*");console.log(data);return{props:{the_questions:data.data}}}exportdefaultfunctionHome({the_questions}){const[showAddQuestionModal,setShowAddQuestionModal]=useState(false);return(<divclassName={styles.container}><Head><title>Q/AApp</title><linkrel="icon"href="/favicon.ico"/></Head><mainclassName={styles.main}><divclassName={styles.breadcrumb}><div><spanstyle={{margin:"1px"}}><buttonstyle={{backgroundColor:"rgba(185, 43, 39, 1)",border:"1px solid rgba(101, 20, 18, 1)",}}onClick={()=>setShowAddQuestionModal(true)}>AddQuestion</button></span></div></div><divclassName={styles.questioncontainerr}><div>{the_questions?.map((question)=>(<QuestionCardkey={question.id}question={question}/>))}</div></div>{showAddQuestionModal?(<AddQuestionDialogcloseModal={()=>setShowAddQuestionModal((pV)=>!pV)}/>):null}</main></div>);}
Enter fullscreen modeExit fullscreen mode

Here, we’re usinggetServerSideProps to fetch our questions withaxios from the[http://localhost:1337/api/questions?populate=*](http://localhost:1337/questions) endpoint. We return the questions asthe_questions from thegetServerSideProps in theprops object, which we can access inHome component.

Note: The`populate=` query parameter allows us to fetch all fields relation, media and components fields

We have theshowAddQuestionModal state. TheshowAddQuestionModal state is used to toggle the display of theAddQuestionsModal on and off.

In the UI, we have anAdd Question button that will set theshowAddQuestionModal state to true when clicked. This will make theAddQuestionDialog modal show up.

The questions in fromthe_questions prop are rendered. Each question in thethe_questions array is rendered by aQuestionCard component. Now, we will create two components:QuestionCard andAddQuestionDialog.

QuestionCard

This presentational component will accept a question object and render a minimal detail of it. Let's create the files and folder.

mkdircomponents/QuestionCardtouchcomponents/QuestionCard/index.jstouchcomponents/QuestionCard/QuestionCard.module.css
Enter fullscreen modeExit fullscreen mode

Open theindex.js and paste the below code:

importstylesfrom"./QuestionCard.module.css";importLinkfrom"next/link";exportdefaultfunctionQuestionCard({question}){const{id}=questionconst{qText,user,answers}=question.attributes;return(<divclassName={styles.question}><divclassName={styles.questiondetails}><divstyle={{display:"flex",alignItems:"center",}}><spanstyle={{display:"block",width:"35px",height:"35px",backgroundColor:"grey",borderRadius:"50%",}}></span><spanstyle={{paddingLeft:"4px"}}>{user}</span></div><Linkhref={`questions/${id}`}><divclassName={styles.questionbalance}style={{cursor:"pointer"}}><h3>{qText}</h3></div></Link><divstyle={{display:"flex",alignItems:"center",color:"grey"}}title="Answers"><MsgIcon/><spanstyle={{paddingLeft:"6px"}}>{answers.length}</span></div></div></div>);}exportfunctionMsgIcon(){return(<svgwidth="24px"height="24px"viewBox="0 0 24 24"><gid="comment"className="icon_svg-stroke icon_svg-fill"stroke="#666"strokeWidth="1.5"fill="none"fillRule="evenodd"><pathd="M12.0711496,18.8605911 C16.1739904,18.8605911 19.5,15.7577921 19.5,11.9302955 C19.5,8.102799 16.1739904,5 12.0711496,5 C7.96830883,5 4.64229922,8.102799 4.64229922,11.9302955 C4.64229922,13.221057 5.02055525,14.429401 5.67929998,15.4641215 C5.99817082,15.9649865 4.1279592,18.5219189 4.56718515,18.9310749 C5.02745574,19.3598348 7.80252458,17.6358115 8.37002246,17.9406001 C9.45969688,18.5258363 10.7235179,18.8605911 12.0711496,18.8605911 Z"></path></g></svg>);}
Enter fullscreen modeExit fullscreen mode

We destructuredquestion from theprop args.
Next, we destructured the id,qText, user, andanswers from thequestion object. Thisquestion object contains the question details. The component then renders these details.

AddQuestionDialog

This component is a modal. New questions are created and added to this system from this component. Let's create the files:

mkdircomponents/AddQuestionDialogtouchcomponents/AddQuestionDialog/index.js
Enter fullscreen modeExit fullscreen mode

Open theindex.js and paste the below code:

import{useState}from"react";importaxiosfrom"axios";exportdefaultfunctionAddQuestionDialog({closeModal}){const[disable,setDisable]=useState(false);asyncfunctionaddQuestion(){setDisable(true);constqText=window.question.value;constuser=window.user.value;// add questionawaitaxios.post("http://localhost:1337/api/questions",{data:{qText,user,}});setDisable(false);closeModal();location.reload();}return(<divclassName="modal"><divclassName="modal-backdrop"onClick={closeModal}></div><divclassName="modal-content"><divclassName="modal-header"><h3>AddNewQuestion</h3><spanstyle={{padding:"10px",cursor:"pointer"}}onClick={closeModal}>X</span></div><divclassName="modal-body content"><divstyle={{display:"flex",flexDirection:"column"}}><divclassName="inputField"><divclassName="label"><label>User</label></div><div><inputid="user"type="text"/></div></div><divclassName="inputField"><divclassName="label"><label>Question:</label></div><div><inputid="question"type="text"placeholder="Start your question with 'What', 'How', 'Why', etc"/></div></div></div></div><divclassName="modal-footer"><buttondisabled={disable}className="btn-danger"onClick={closeModal}>Cancel</button><buttondisabled={disable}className="btn"onClick={addQuestion}>AddQuestion</button></div></div></div>);}
Enter fullscreen modeExit fullscreen mode

We have a state here,disable. This state is used to toggle the disable state of the button in the UI. Looking at the UI, we have two inputs. The first input holds the name of the user that will ask the question and the second input is where the question will be typed.

TheAdd Question will call theaddQuestion function. ThisaddQuestion function will get the values from theuser andquestion inputs, it will use it to call thehttp://localhost:1337/questions endpoint.

We passed the question and user texts as payload to the HTTP POST call, this creates the question in the Strapi backend.

Create Question View

This page will display a specified question along with its answers and comments to the answers.
Let's create the files:

mkdirpages/questionstouchpages/questions/[id].js
Enter fullscreen modeExit fullscreen mode

Open[id].js and paste the below code:

importstylesfrom"../../styles/QuestionView.module.css";import{useRouter}from"next/router";importaxiosfrom"axios";import{useEffect,useState,useRef}from"react";import{MsgIcon}from"../../components/QuestionCard";exportconstgetServerSideProps=async({params})=>{const{id}=paramsconstquestion=awaitaxios.get(`http://localhost:1337/api/questions/${id}?populate=*`);const{data:{attributes:{answers}}}=question.dataconstcomments=awaitaxios.get(`http://localhost:1337/api/comments?populate=*`);console.log(question);console.log(answers);return{props:{id,question:question.data.data,answers:answers.data,comments:comments.data.data}}}exportdefaultfunctionQuestion({id,question,answers,comments}){constrouter=useRouter();const[showAnswerQuestionSection,setAnswerQuestionSection]=useState(false);asyncfunctiondeleteQuestion(){if(confirm("Do you really want to delete this question?")){awaitaxios.delete(`http://localhost:1337/api/questions/${id}`);router.push("/");}}return(<divclassName={styles.questionviewcontainer}><divclassName={styles.questionviewmain}><divstyle={{width:"100%"}}><divclassName={styles.questionviewname}><h1>{question?.attributes.qText}</h1></div><divclassName={styles.questionviewminidet}><divstyle={{display:"flex"}}><span><buttononClick={()=>setAnswerQuestionSection((pV)=>!pV)}className="btn-danger"style={{backgroundColor:"unset",color:"black",border:"unset",display:"flex",alignItems:"center",paddingLeft:"0",}}><AnswerIcon/><spanstyle={{paddingLeft:"6px"}}>Answer</span></button></span><span><buttononClick={deleteQuestion}className="btn-danger"style={{backgroundColor:"unset",color:"black",border:"unset",display:"flex",alignItems:"center",}}><DeleteIcon/><spanstyle={{paddingLeft:"6px"}}>Delete</span></button></span></div></div><div>{showAnswerQuestionSection?(<AnswerQuestionSectionquestion={question}/>):null}</div><divclassName={styles.questionviewtransactionscont}><divclassName={styles.questionviewtransactions}><h2>{answers?.length}Answers</h2></div><divclassName={styles.questionviewtransactionslist}style={{padding:"unset"}}>{!answers||answers?.length<=0?"No Answers yet.":answers?.map((answer,i)=><Answerkey={answer.id}answer={answer}comments={comments}/>)}</div></div></div></div></div>);}
Enter fullscreen modeExit fullscreen mode

This component gets the id of the question from the router object. This id is used in theuseEffect hook callback to call the endpointhttp://localhost:1337/api/questions/${id}.
This will return the question with its details.

The UI displays the question details and the answers to the question. The comments of the answers are also all displayed.

ThedeleteQuestion function deletes the question from our Strapi backend. It calls thehttp://localhost:1337/api/questions/${id} endpoint with the id of the question, the HTTP verb is DELETE which will delete the question from the backend.

TheAnswerQuestionSection component is where the answer to the question is typed. This component is toggled by theshowAnswerQuestionSection state, this state is manipulated by theMsgIcon.

Let's see the code for thisAnswerQuestionSection component:

functionAnswerQuestionSection({question}){varoptions={placeholder:"Write your answer",readOnly:false,theme:"snow",};consteditorRef=useRef();constuserRef=useRef();const[disable,setDisable]=useState(false);const[q,setQuill]=useState();useEffect(()=>{if(q)return;const_q=newQuill(editorRef.current,options);setQuill(_q);},[q]);functionanswerQuestion(){setDisable(true);axios.post("http://localhost:1337/api/answers",{data:{aText:q.getText(),user:userRef.current.value,question:question?.id,}});setDisable(false);window.location.reload();}return(<divstyle={{marginTop:"16px",backgroundColor:"white",}}><><div><inputtype="text"placeholder="Enter user here..."ref={userRef}/></div><divname="editor"ref={editorRef}style={{backgroundColor:"white"}}></div><divstyle={{display:"flex",justifyContent:"flex-end",padding:"14px",}}><buttonstyle={{borderRadius:"14px"}}onClick={answerQuestion}disabled={disable}>Post</button></div></></div>);}
Enter fullscreen modeExit fullscreen mode

This component initializes a Quill editor in thediv[name="editor"]. We have an input that collects the name of the user answering the question. ThePost button will call theanswerQuestion function when clicked. ThisanswerQuestion function will call the endpointhttp://localhost:1337/answers. The answer text, user name, and the question id are sent to the endpoint call as payload. The page is reloaded to reflect the new additions.

Let's code theAnswer component:

functionAnswer({answer,comments}){const{id}=answerconst{aText,user}=answer.attributes;console.log({comments});const[_comments,setComments]=useState(comments?comments.filter(comment=>comment.attributes.answer.data?.id==id):[]);console.log(id,comments);const[showCommentInput,setShowCommentInput]=useState(false);constcommentRef=useRef();constuserRef=useRef();asyncfunctionaddComment(){constresultData=awaitaxios.post("http://localhost:1337/api/comments",{data:{cText:commentRef.current.value,user:userRef.current.value,answer:id,}});setShowCommentInput(false);window.location.reload();}return(<divclassName={styles.question}style={{borderBottom:"1px solid rgba(234, 238, 243, 1)",padding:"14px",}}><divclassName={styles.questiondetails}><divstyle={{display:"flex",alignItems:"center",}}><spanstyle={{display:"block",width:"35px",height:"35px",backgroundColor:"grey",borderRadius:"50%",}}></span><spanstyle={{paddingLeft:"4px"}}>{user}</span></div><divclassName={styles.questionbalance}style={{cursor:"pointer",paddingTop:"24px",paddingBottom:"24px",}}><span>{aText}</span></div><divstyle={{display:"flex",alignItems:"center",color:"grey",cursor:"pointer",}}onClick={()=>setShowCommentInput((pV)=>!pV)}><MsgIcon/><spanstyle={{paddingLeft:"6px"}}>{_comments?.length}</span></div><div>{showCommentInput?(<divstyle={{display:"flex",flexDirection:"row",alignItems:"center",marginTop:"9px",}}><inputtype="text"placeholder="Enter user..."style={{borderRadius:"9px",width:"110px",marginRight:"3px",}}ref={userRef}/><inputtype="text"placeholder="Add comment..."style={{borderRadius:"9px"}}ref={commentRef}/><buttonstyle={{borderRadius:"19px",fontSize:"14px",fontWeight:"bolder",boxSizing:"content-box",}}onClick={addComment}><divstyle={{display:"flex",whiteSpace:"nowrap"}}>AddComment</div></button></div>):null}</div><divstyle={{paddingTop:"14px",marginLeft:"23px",marginBottom:"14px",}}>{_comments?.map((comment)=>(<Commentkey={comment.id}comment={comment}/>))}</div></div></div>);}
Enter fullscreen modeExit fullscreen mode

This component is passed ananswer object in its props object args. It uses theid from theanswer object to filter all the fetched comments connected to the answer.

We have ashowCommentInput state to toggles a UI section. This section is where users can comment on an answer. This section opens when theMsgIcon beneath each answer is clicked. The comment section has two inputs that hold the commenter's name and the comment text, then theAdd Comment button adds the comment to the answer.

ThisAdd Comment button calls theaddComment function. ThisaddFunction function makes an HTTP POST call to thehttp://localhost:1337/api/comments/ endpoint, the comment text, user name, and the answer id is sent also as the POST payload. This activity creates a new comment in our Comment collection.

The comments in the answer are rendered in this component, each comment is handled by theComment component. Let's code the component:

functionComment({comment}){const{user,cText}=comment.attributes;return(<divclassName={styles.question}style={{backgroundColor:"rgba(234, 238, 243, 1)",padding:"14px",marginBottom:"4px",}}><divclassName={styles.questiondetails}><divstyle={{display:"flex",alignItems:"center",}}><spanstyle={{display:"block",width:"35px",height:"35px",backgroundColor:"grey",borderRadius:"50%",}}></span><spanstyle={{paddingLeft:"4px"}}>{user}</span></div><divclassName={styles.questionbalance}style={{cursor:"pointer",paddingTop:"14px",paddingBottom:"14px",}}><span>{cText}</span></div></div></div>);}
Enter fullscreen modeExit fullscreen mode

This component accepts acomment object in its props arg. Thiscomment object contains the comment of an answer to render. The commenter and the comment text is de-structured from thecomment object. These are then displayed in the UI.

Test the App

Add new question:

Add new question

new question added

View a question:

View a question

Answer a question:

Answer a question

Answer submitted

Comment on an answer:

Comment on an answer

comment to answer submitted

Delete a question:

Delete a question

Question deleted

Source Code

Find the source code of the project below:

Conclusion

We covered a lot in this tutorial. We started by introducing relationships in the database and going through the types of relationships one after the other explaining what they are. Next, we saw how we can set up a Strapi project and how we can establish relations in Strapi collections.

Finally, we built a QnA app just like Quora to fully demonstrate how relations in Strapi can be used in a real-life app. This article is a goldmine of Strapi info.

Top comments(0)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Do you want to contribute to open source and see what's in Strapi?

All of Strapi is available on GitHub.

Check it out!

More fromStrapi

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp