Create a Quiz App Using Appwrite and Remix
Tags: appwrite, remix, beginners, react
Published: 2023-01-13 10:44:29

Data is a valuable resource used by individuals, organizations, and institutions; it is also crucial to offer sufficient technical security to stop unauthorized users from obtaining these data.

A robust database management system like Appwrite can provide the requirements necessary for safeguarding data. It enables authorized users to contribute new data, update existing data, and remove obsolete data.

This post discusses how to build a Remix application using [Appwrite’s](https://appwrite.io/?utm_source=hackmamba&utm_medium=hackmamba-blog) out-of-box functionalities to fetch and render data from its database.

[Remix](https://remix.run/) is a full-stack React framework used to create web applications. It offers several valuable features, including built-in support for cookies and sessions, server-side rendering, file system-based routing, TypeScript support, and more.

The complete source code of this project is on [GitHub](https://github.com/ugwutotheeshoes/quiz-app). Fork it to get started quickly.

###Prerequisite

To comfortably follow along in this article, it would be helpful to have the following:

- Docker Desktop installed on the computer; run the `docker -v` command to verify that we have Docker installed. If not, install it from the [Get Docker documentation](https://docs.docker.com/get-docker/).
- A basic understanding of JavaScript, React.js, and [Remix](https://remix.run/).
- An Appwrite instance; check out this [article](https://dev.to/hackmamba/create-a-local-appwrite-instance-in-3-steps-19n9?utm_source=hackmamba&utm_medium=hackmamba) on how to set up an instance. Appwrite also supports one-click install on [DigitalOcean](https://marketplace.digitalocean.com/apps/appwrite) or [Gitpod](https://gitpod.io/#https://github.com/appwrite/integration-for-gitpod).
- Node and its package manager, `npm`. Run the command `node -v && npm -v` to verify that we have them installed or install them from [here](https://nodejs.org/en/).


##Getting Started
###Project setup and installation

Run the following command in the terminal to create a new Remix application:

```js
    npx create-remix@latest <project-name>
```
The command above triggers a CLI (Command Line Interface) where we can create our Remix application. The image below shows the configuration options the CLI provides:

![Configuration options](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7y6xqmpib88qu03936s2.png)

Then, navigate into the project directory cd <project-name> and run npm run start to start a development server at [https://localhost:3000/](https://localhost:3000/) in our browser.

> **NOTE**: <project-name> above stands for the name of our app, call it any name that makes sense.


###Setting up an Appwrite Project

####Installing Appwrite
[Appwrite](https://appwrite.io/?utm_source=hackmamba&utm_medium=hackmamba-blog) is a development platform that provides a powerful API and management console for building backend servers for web and mobile applications.

To use Appwrite in our Remix application, install the Appwrite client-side SDK (Software Development Kit) for web applications.

```js
    npm install appwrite
```
Then, we’ll set up a new Appwrite project by starting an Appwrite instance and navigating to the specified hostname and port `http://localhost:80`.


> **NOTE**: To set up an Appwrite instance, follow the steps in the article located in **Prerequisites**.

####Creating a New Project
Next, we need to log into our account or create an account if we don’t have one.

![Log in or sign up](https://paper-attachments.dropboxusercontent.com/s_52ABD050904341011DEBD07E3B7A89DB41271D9A224427484F2A5E59DF19874A_1671311391345_Sign-up-Appwrite.png)


On Appwrite’s console, click the **Create Project** button, input `quiz-app` as the project name, and click **Create**.


![Creating a new project](https://paper-attachments.dropboxusercontent.com/s_52ABD050904341011DEBD07E3B7A89DB41271D9A224427484F2A5E59DF19874A_1671306129565_Organizations-Appwrite.png)


The project dashboard will appear on the console. Next, copy the **Project ID**, we’ll use this to set up our Remix application.


![Project ID](https://paper-attachments.dropboxusercontent.com/s_52ABD050904341011DEBD07E3B7A89DB41271D9A224427484F2A5E59DF19874A_1671309815324_Console-Appwrite.png)


####Creating a Collection and Attribute
On the left side of the Appwrite Console dashboard, click on the **Database** tab. Click on the **Create Database** button to create a new database. Creating a new database will lead us to the **Collection** page.

![Create a database](https://paper-attachments.dropboxusercontent.com/s_52ABD050904341011DEBD07E3B7A89DB41271D9A224427484F2A5E59DF19874A_1671310043425_Databases-Appwrite.png)
![Database name](https://paper-attachments.dropboxusercontent.com/s_52ABD050904341011DEBD07E3B7A89DB41271D9A224427484F2A5E59DF19874A_1671310047351_Databases-Appwrite+1.png)


Next, we’ll create a collection in our database by clicking the **Add Collection** button. 

![Create a Collection](https://paper-attachments.dropboxusercontent.com/s_52ABD050904341011DEBD07E3B7A89DB41271D9A224427484F2A5E59DF19874A_1671310213661_Database-Appwrite.png)


After creating a **Collection**, go to the **Update Permissions** section on the **Settings** page. We want to assign a **Read Access** and **Write Access** with a **role: any** value. We can customize these roles later to specify who has access to read or write to our database.

![Update Permission](https://paper-attachments.dropboxusercontent.com/s_52ABD050904341011DEBD07E3B7A89DB41271D9A224427484F2A5E59DF19874A_1671310804785_questions-Appwrite.png)


On the **Collection** dashboard, copy the **Collection ID**; we’ll need to perform operations on the collection’s documents.

Next, go to the **Attributes** tab to create the properties we want the document to have. Let’s generate string attributes — **options**, **answers**, and **questions** with a size of 256 bits.

![Create an attribute](https://paper-attachments.dropboxusercontent.com/s_52ABD050904341011DEBD07E3B7A89DB41271D9A224427484F2A5E59DF19874A_1671573220777_questions-Appwrite+7.png)
![Created attributes](https://paper-attachments.dropboxusercontent.com/s_52ABD050904341011DEBD07E3B7A89DB41271D9A224427484F2A5E59DF19874A_1671573139247_questions-Appwrite+6.png)

> **NOTE**: The quiz options have four attributes (one, two, three, & four) for our application.

Let’s head to the **Documents** section and click on **Create Document**. We want to create a database for our application’s questions, options, and answers.


![Creating a document](https://paper-attachments.dropboxusercontent.com/s_52ABD050904341011DEBD07E3B7A89DB41271D9A224427484F2A5E59DF19874A_1671317576245_question-Appwrite.png)
![Created documents](https://paper-attachments.dropboxusercontent.com/s_52ABD050904341011DEBD07E3B7A89DB41271D9A224427484F2A5E59DF19874A_1671573282131_questions-Appwrite+8.png)

###Building the app component

Open the `app/routes/index.jsx` file and replace its default syntax with the code snippet below.

```js
// index.jsx
export default function Index() {
 return (
     <div className="container">
       <h1>Remix Science Quiz App</h1>
       <p>Easy learning with 10th grade science questions and answers that covers all the important topics.</p>
       <button className="quiz-btn">start quiz</button>
     </div>
   )};    
```
This renders a default home page which will look like this:


![Default Home Page](https://paper-attachments.dropboxusercontent.com/s_52ABD050904341011DEBD07E3B7A89DB41271D9A224427484F2A5E59DF19874A_1671543270363_New-Remix-App.png)

Next, we'll create and import a `Questions.jsx` file into the `index.jsx` file. This component houses our quiz questions and options. We also want our quiz questions to appear only when we’ve started the quiz. We’ll use a `useState` variable and conditional rendering to achieve this functionality, where `toShow`, the `useState` variable, displays or hides either of the components, as shown below. 

```js
 //index.jsx
import Questions from "./Questions";

export default function Index() {
 const [toShow, setToShow] = useState(true);
  return (
  <div>
    {toShow ? (
      <div className="container">
       // Default Home Page
      </div>
       ) : (
       <Questions  />
     )}
  </div>
)};
```     

###Fetching and Rendering Data from Appwrite’s Database

To fetch data from Appwrite’s database, add the code snippet below to our `index.jsx` file.

```js
// index.jsx
import { Client, Databases } from "appwrite";

export default function Index() {
 const [quizQuestions, setQuizQuestions] = useState([""]);
 const [toShow, setToShow] = useState(true);
 const client = new Client();
 const databases = new Databases(client);
   client
    .setEndpoint("OUR_API_ENDPOINT") // Your API Endpoint
    .setProject("OUR_PROJECT_ID"); // Your project ID
 const promise = databases.listDocuments( "OUR_DATABASE_ID", "OUR_COLLECTION_ID");
 const updateData = () => {
   promise.then(
   function (response) {
     setQuizQuestions(response.documents);
     },
   function (error) {
     console.log(error); // Failure
    });
     setToShow(false);
  };
  return (
   <div>
     {toShow ? (
       <div className="container">
          <h1>Remix Science Quiz App</h1>
          <p> Easy learning with 10th grade science questions and answers that covers all the important topics. </p>
          <button className="quiz-btn" onClick={updateData}> start quiz </button>
       </div>
          ) : (
       <Questions quizQuestions={quizQuestions} />
       )}
    </div>
   );
 }
```
 
 The code snippet above does the following:

- Create and assign a function that triggers a request to Appwrite’s servers
- Makes a request to Appwrite’s servers to fetch the quiz data and push the fetched data into a `useState` variable
- Export our quiz data to the `Questions.jsx` component


> **NOTE**: Get your `API Endpoint`, `Project ID`,  `[DATABASE_ID]`, `[COLLECTION_ID]`, and `[DOCUMENT_ID]` from Appwrite’s console.

 
Here, we’ll render the data imported from Appwrite. The generated data will be structured so that a user can answer the quiz questions one at a time. To do this, let’s use a `useState` variable, `currentQuestion`, to show only the quiz questions and options that match the variable’s value. 
 
```js
// Questions.jsx
const Questions = ({ quizQuestions }) => {
const [currentQuestion, setCurrentQuestion] = useState(0);
const [finalResult, setFinalResult] = useState(true);
 return (
<div className="questions">
  <div className="section-container">
    <h2>Questions {currentQuestion + 1} out of {quizQuestions.length}</h2>
     <p>{quizQuestions[currentQuestion].question}?</p>
      <div className="section">
       <button name="one">{quizQuestions[currentQuestion].one}</button>
       <button name="two">{quizQuestions[currentQuestion].two}</button>
       <button name="three">{quizQuestions[currentQuestion].three}</button>
       <button name="four">{quizQuestions[currentQuestion].four}</button>
      </div>
  </div>
    <button type="button" className="btn">Next &gt;</button>
 </div>
```

This is how the quiz section of our application will look like:

![Quiz Section](https://paper-attachments.dropboxusercontent.com/s_52ABD050904341011DEBD07E3B7A89DB41271D9A224427484F2A5E59DF19874A_1671566880199_New-Remix-App+1.png)

###Generating the Quiz Score 

Let’s create a function — `handleChange`, to check whether the option a user clicks on is correct. The function matches the clicked option’s text with the answer from Appwrite’s database. If they both match, we‘ll add a point to our quiz score variable called `score`.

```js
     // Questions.jsx
      const Questions = ({ quizQuestions }) => {  
      const [currentQuestion, setCurrentQuestion] = useState(0);
      const [finalResult, setFinalResult] = useState(true);
      const [score, setScore] = useState(0);
      const handleChange = (e) => {
        e.preventDefault();
        if (e.target.innerText === quizQuestions[currentQuestion].answer) {
          setScore(score + 1);
        }
      };
    return (
    <div className="questions">
        <div className="section-container">
           <h2>Questions {currentQuestion + 1} out of {quizQuestions.length}</h2>
           <p>{quizQuestions[currentQuestion].question}?</p>
          <div className="section">
            <button onClick={handleChange}>{quizQuestions[currentQuestion].one}</button>
            <button onClick={handleChange}>{quizQuestions[currentQuestion].two}</button>
            <button onClick={handleChange}>{quizQuestions[currentQuestion].three}</button>
            <button onClick={handleChange}>{quizQuestions[currentQuestion].four}</button>
           </div>
        </div>
        <button type="button" className="btn">Next &gt;</button>
    </div>
```
  
Next, we need to switch to the next question and show the quiz score if there aren't any questions left. 

```js
// Questions.jsx
const Questions = ({ quizQuestions }) => {
const [currentQuestion, setCurrentQuestion] = useState(0);
const [finalResult, setFinalResult] = useState(true);
const handleSubmit = (e) => {
    e.preventDefault();
    if (currentQuestion + 1 < quizQuestions.length) {
     setCurrentQuestion(currentQuestion + 1);
   } else {
    setFinalResult(false);
   }
 };
return (
   <div>
    {finalResult ? (
    <div className="questions">
     // Question Section
     <button type="button" className="btn" onClick={handleSubmit}>Next &gt;</button>
     </div>
  ) : (
     <div className="final">
       <p>Your final score is {score} out of {quizQuestions.length}</p>
        <button type="button" onClick={() => window.location.reload()}>
         reset score!
        </button>
     </div>
   )}
 </div>
)};

export default Questions;
```

Let’s assign a function to the `next` button. This function first checks if there’s a question to show; if there is, it displays the question, and if there’s not, it shows the quiz score and a button to reset the quiz score. 
This is how the application will look after applying the significant configurations:


![Our Application](https://paper-attachments.dropboxusercontent.com/s_52ABD050904341011DEBD07E3B7A89DB41271D9A224427484F2A5E59DF19874A_1671575051676_ezgif.com-gif-maker+17.gif)

##Conclusion
This article discussed how to use Appwrite’s storage functionality and, more importantly, how to integrate [Appwrite](https://appwrite.io/?utm_source=hackmamba&utm_medium=hackmamba-blog) into web applications built with Remix.

##Resources
- [Creating an Appwrite Instance in 3 steps](https://dev.to/hackmamba/create-a-local-appwrite-instance-in-3-steps-19n9?utm_source=hackmamba&utm_medium=hackmamba)
- [Appwrite’s Web Documentation](https://appwrite.io/docs/getting-started-for-web)
- [Appwrite Database Web SDK](https://appwrite.io/docs/client/databases)
- [Remix Documentation](https://remix.run/docs/en/v1)