Build a Virtual Art Gallery with Reactjs, GraphQL & MongoDB

Avatar

By squashlabs, Last Updated: Sept. 16, 2023

Build a Virtual Art Gallery with Reactjs, GraphQL & MongoDB

In this article, we will explore how to build a virtual art gallery application using React.js, GraphQL, and MongoDB. The application will allow artists to upload their artwork, and users will be able to explore the gallery using a React.js front-end. We will use GraphQL to enable users to filter and search for artwork based on various criteria. MongoDB will be used to store the artwork data, and we will leverage Docker for easy deployment and scaling of the application.

Project Requirements and Dependencies

Before we dive into building the virtual art gallery, let's first discuss the project requirements and the dependencies we will be using.

The following are the requirements for our virtual art gallery application:

- Artists should be able to upload their artwork, including images and additional information such as the title, artist name, medium, and price.

- Users should be able to explore the gallery and view the artwork.

- Users should be able to filter and search for artwork based on criteria such as artist name, medium, and price range.

To fulfill these requirements, we will be using the following dependencies:

- React.js: A popular JavaScript library for building user interfaces.

- GraphQL: A query language for APIs that provides a flexible and efficient way to request and manipulate data.

- Apollo Client: A fully-featured GraphQL client for React.js that makes it easy to interact with a GraphQL API.

- MongoDB: A NoSQL database that provides a flexible and scalable solution for storing and retrieving data.

- Docker: A platform that allows us to package and distribute our application in lightweight, portable containers.

Related Article: Build a Movie Search App with GraphQL, Node & TypeScript

Now that we have defined our project requirements and dependencies, let's start building the React.js front-end for our virtual art gallery.

To get started, we need to set up a new React.js project. Assuming you have Node.js installed, open your terminal and run the following command:

npx create-react-app virtual-art-gallery

This will create a new directory called "virtual-art-gallery" with a basic React.js project structure.

Next, navigate to the project directory:

cd virtual-art-gallery

We will be using Apollo Client to interact with the GraphQL API, so let's install the necessary dependencies:

npm install apollo-boost @apollo/react-hooks graphql

Once the dependencies are installed, we can start building our React components. Create a new file called "ArtGallery.js" in the "src" directory and add the following code:

import React from 'react';
import { useQuery } from '@apollo/react-hooks';
import { gql } from 'apollo-boost';

const GET_ARTWORKS = gql`
  query GetArtworks {
    artworks {
      id
      title
      artist
      medium
      price
      image
    }
  }
`;

const ArtGallery = () => {
  const { loading, error, data } = useQuery(GET_ARTWORKS);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <div>
      <h1>Virtual Art Gallery</h1>
      <div className="artworks">
        {data.artworks.map((artwork) => (
          <div key={artwork.id} className="artwork">
            <img src={artwork.image} alt={artwork.title} />
            <h2>{artwork.title}</h2>
            <p>{artwork.artist}</p>
            <p>{artwork.medium}</p>
            <p>{artwork.price}</p>
          </div>
        ))}
      </div>
    </div>
  );
};

export default ArtGallery;

In this code, we define a GraphQL query called "GET_ARTWORKS" that fetches the artwork data from the server. We use the "useQuery" hook from Apollo Client to execute the query and retrieve the data. The loading and error states are also handled, displaying a loading message or an error message if necessary.

To render the artwork data, we map over the "data.artworks" array and display the image, title, artist, medium, and price for each artwork.

Now let's update the "App.js" file to include the "ArtGallery" component:

import React from 'react';
import ArtGallery from './ArtGallery';

function App() {
  return (
    <div className="App">
      <ArtGallery />
    </div>
  );
}

export default App;

Finally, start the development server by running the following command:

npm start

You should now see the virtual art gallery displayed in your browser.

Integrating GraphQL for Filtering and Searching Artwork

To enable users to filter and search for artwork, we need to integrate GraphQL into our virtual art gallery application.

First, let's set up the GraphQL server. In the root directory of your project, create a new file called "server.js" and add the following code:

const { ApolloServer, gql } = require('apollo-server');

// Sample artwork data
const artworks = [
  {
    id: '1',
    title: 'Sunflower',
    artist: 'Vincent van Gogh',
    medium: 'Oil on canvas',
    price: '€1,000',
    image: 'https://example.com/sunflower.jpg',
  },
  {
    id: '2',
    title: 'The Starry Night',
    artist: 'Vincent van Gogh',
    medium: 'Oil on canvas',
    price: '€2,000',
    image: 'https://example.com/starry-night.jpg',
  },
  // more artworks...
];

// GraphQL schema
const typeDefs = gql`
  type Artwork {
    id: ID!
    title: String!
    artist: String!
    medium: String!
    price: String!
    image: String!
  }

  type Query {
    artworks: [Artwork!]!
  }
`;

// GraphQL resolvers
const resolvers = {
  Query: {
    artworks: () => artworks,
  },
};

// Apollo Server
const server = new ApolloServer({ typeDefs, resolvers });

server.listen().then(({ url }) => {
  console.log(`Server running at ${url}`);
});

In this code, we define a GraphQL schema using the "gql" function from Apollo Server. The schema includes a "Artwork" type with fields for the artwork data, and a "Query" type with a single field "artworks" that returns an array of artworks.

We also define a resolver for the "artworks" field, which simply returns the "artworks" array we defined earlier.

To start the GraphQL server, run the following command:

node server.js

You should see a message indicating that the server is running.

Now let's update our React.js front-end to fetch the artwork data from the GraphQL server.

In the "ArtGallery.js" file, replace the existing code with the following:

import React, { useState } from 'react';
import { useQuery } from '@apollo/react-hooks';
import { gql } from 'apollo-boost';

const GET_ARTWORKS = gql`
  query GetArtworks($artist: String, $medium: String, $priceRange: String) {
    artworks(artist: $artist, medium: $medium, priceRange: $priceRange) {
      id
      title
      artist
      medium
      price
      image
    }
  }
`;

const FILTER_OPTIONS = [
  { label: 'All Artists', value: null },
  { label: 'Vincent van Gogh', value: 'Vincent van Gogh' },
  // more artist options...
];

const ArtGallery = () => {
  const [artist, setArtist] = useState(null);
  const [medium, setMedium] = useState(null);
  const [priceRange, setPriceRange] = useState(null);

  const { loading, error, data } = useQuery(GET_ARTWORKS, {
    variables: { artist, medium, priceRange },
  });

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <div>
      <h1>Virtual Art Gallery</h1>
      <div className="filters">
        <select value={artist} onChange={(e) => setArtist(e.target.value)}>
          {FILTER_OPTIONS.map((option) => (
            <option key={option.value} value={option.value}>
              {option.label}
            </option>
          ))}
        </select>
        {/* Add more filters */}
      </div>
      <div className="artworks">
        {data.artworks.map((artwork) => (
          <div key={artwork.id} className="artwork">
            <img src={artwork.image} alt={artwork.title} />
            <h2>{artwork.title}</h2>
            <p>{artwork.artist}</p>
            <p>{artwork.medium}</p>
            <p>{artwork.price}</p>
          </div>
        ))}
      </div>
    </div>
  );
};

export default ArtGallery;

In this updated code, we add state variables for the artist, medium, and price range filters. We also update the "GET_ARTWORKS" query to accept these variables. The variables are then passed to the useQuery hook.

We add a select element for the artist filter, using the FILTER_OPTIONS array to generate the options. You can add more filters as needed based on your requirements.

Now when you select an artist from the dropdown, the artwork data will be refetched with the selected artist filter applied.

Storing Artwork Data in MongoDB

To store the artwork data in MongoDB, we need to set up a MongoDB database and connect it to our virtual art gallery application.

First, make sure you have MongoDB installed and running on your machine. You can download MongoDB from the official website and follow the installation instructions.

Once MongoDB is installed, open a new terminal window and start the MongoDB server:

mongod

Next, we need to create a new MongoDB database and collection for our artwork data. Open another terminal window and run the following command to start the MongoDB shell:

mongo

In the MongoDB shell, run the following commands to create a new database and collection:

use virtual-art-gallery
db.createCollection('artworks')

Now that we have set up the database and collection, let's update our GraphQL server to store the artwork data in MongoDB.

In the "server.js" file, replace the existing code with the following:

const { ApolloServer, gql } = require('apollo-server');
const { MongoClient } = require('mongodb');

const MONGODB_URI = 'mongodb://localhost:27017';
const MONGODB_DB_NAME = 'virtual-art-gallery';

let db;

MongoClient.connect(MONGODB_URI, (err, client) => {
  if (err) {
    console.error(err);
    process.exit(1);
  }

  db = client.db(MONGODB_DB_NAME);

  // Apollo Server
  const server = new ApolloServer({
    typeDefs,
    resolvers,
    context: { db },
  });

  server.listen().then(({ url }) => {
    console.log(`Server running at ${url}`);
  });
});

// GraphQL schema and resolvers...

In this updated code, we import the MongoClient from the MongoDB driver and define the MongoDB URI and database name.

We then use the MongoClient to connect to the MongoDB server and assign the database to the "db" variable. The "db" variable is passed to the Apollo Server context, making it accessible to the resolvers.

Now we can update the resolvers to interact with the MongoDB database. Replace the resolver for the "artworks" field with the following code:

Query: {
  artworks: async (_, { artist, medium, priceRange }, { db }) => {
    const filters = {};

    if (artist) {
      filters.artist = artist;
    }

    if (medium) {
      filters.medium = medium;
    }

    if (priceRange) {
      const [minPrice, maxPrice] = priceRange.split('-');
      filters.price = { $gte: minPrice, $lte: maxPrice };
    }

    return await db.collection('artworks').find(filters).toArray();
  },
},

In this code, we define a "filters" object based on the provided filter values. If an artist filter is provided, we add it to the filters object. The same goes for the medium and price range filters.

We then use the "find" method of the MongoDB collection to retrieve the artworks that match the filters. The results are returned as an array.

Restart the GraphQL server by stopping the previous process and running the following command:

node server.js

Now the artwork data will be stored and retrieved from the MongoDB database.

Related Article: How to Uninstall npm Modules in Node.js

Using Docker for Deployment and Scaling

Docker allows us to package and distribute our virtual art gallery application in lightweight, portable containers. It also provides easy deployment and scaling capabilities, making it an ideal choice for our application.

To use Docker, we need to create a Dockerfile that specifies the instructions for building the Docker image.

In the root directory of your project, create a new file called "Dockerfile" and add the following code:

# Use an official Node.js runtime as the base image
FROM node:14

# Set the working directory in the container
WORKDIR /usr/src/app

# Copy package.json and package-lock.json to the working directory
COPY package*.json ./

# Install the dependencies
RUN npm install

# Copy the rest of the application code to the working directory
COPY . .

# Build the React.js app
RUN npm run build

# Expose the port to the outside world
EXPOSE 3000

# Define the command to run the application
CMD [ "npm", "start" ]

In this Dockerfile, we start with the official Node.js runtime as the base image. We set the working directory inside the container, copy the package.json and package-lock.json files to the working directory, and install the dependencies using npm install.

We then copy the rest of the application code to the working directory and build the React.js app using npm run build.

The EXPOSE instruction exposes port 3000, which is the default port used by the React.js development server.

Finally, we define the CMD instruction to run the application using npm start.

To build the Docker image, open a new terminal window and run the following command in the root directory of your project:

docker build -t virtual-art-gallery .

This will build the Docker image with the tag "virtual-art-gallery".

To run the Docker container, use the following command:

docker run -p 3000:3000 virtual-art-gallery

This will start the container and map port 3000 from the container to port 3000 on your host machine.

Now you can access the virtual art gallery application in your browser at http://localhost:3000.

Docker provides a scalable solution for deploying our application. By running multiple instances of the Docker container, we can easily scale our application to handle increased traffic and load.

To enable artists to upload their artwork in the virtual art gallery application, we can add a file upload feature.

First, let's update the GraphQL schema to include a mutation for uploading artwork. Open the "server.js" file and replace the existing schema definition with the following:

const typeDefs = gql`
  type Artwork {
    id: ID!
    title: String!
    artist: String!
    medium: String!
    price: String!
    image: String!
  }

  type Query {
    artworks(artist: String, medium: String, priceRange: String): [Artwork!]!
  }

  type Mutation {
    uploadArtwork(
      title: String!
      artist: String!
      medium: String!
      price: String!
      image: String!
    ): Artwork!
  }
`;

In this updated schema, we define a new mutation called "uploadArtwork" that accepts the title, artist, medium, price, and image fields. The mutation returns the uploaded artwork data.

Next, let's update the resolvers to handle the artwork upload. Replace the resolver for the "uploadArtwork" mutation with the following code:

Mutation: {
  uploadArtwork: async (_, { title, artist, medium, price, image }, { db }) => {
    const artwork = {
      id: new Date().getTime().toString(),
      title,
      artist,
      medium,
      price,
      image,
    };

    await db.collection('artworks').insertOne(artwork);

    return artwork;
  },
},

In this code, we create a new artwork object with the provided fields, including a unique ID generated based on the current timestamp. We then use the "insertOne" method of the MongoDB collection to insert the artwork into the database.

Now let's update our React.js front-end to include an artwork upload form.

In the "ArtGallery.js" file, replace the existing code with the following:

import React, { useState } from 'react';
import { useQuery, useMutation } from '@apollo/react-hooks';
import { gql } from 'apollo-boost';

const GET_ARTWORKS = gql`
  query GetArtworks($artist: String, $medium: String, $priceRange: String) {
    artworks(artist: $artist, medium: $medium, priceRange: $priceRange) {
      id
      title
      artist
      medium
      price
      image
    }
  }
`;

const UPLOAD_ARTWORK = gql`
  mutation UploadArtwork(
    $title: String!
    $artist: String!
    $medium: String!
    $price: String!
    $image: String!
  ) {
    uploadArtwork(
      title: $title
      artist: $artist
      medium: $medium
      price: $price
      image: $image
    ) {
      id
      title
      artist
      medium
      price
      image
    }
  }
`;

const FILTER_OPTIONS = [
  { label: 'All Artists', value: null },
  { label: 'Vincent van Gogh', value: 'Vincent van Gogh' },
  // more artist options...
];

const ArtGallery = () => {
  const [artist, setArtist] = useState(null);
  const [medium, setMedium] = useState(null);
  const [priceRange, setPriceRange] = useState(null);
  const [title, setTitle] = useState('');
  const [artistName, setArtistName] = useState('');
  const [artMedium, setArtMedium] = useState('');
  const [artPrice, setArtPrice] = useState('');
  const [artImage, setArtImage] = useState('');

  const { loading, error, data } = useQuery(GET_ARTWORKS, {
    variables: { artist, medium, priceRange },
  });

  const [uploadArtwork] = useMutation(UPLOAD_ARTWORK, {
    update(cache, { data: { uploadArtwork } }) {
      const { artworks } = cache.readQuery({
        query: GET_ARTWORKS,
        variables: { artist, medium, priceRange },
      });

      cache.writeQuery({
        query: GET_ARTWORKS,
        variables: { artist, medium, priceRange },
        data: { artworks: artworks.concat([uploadArtwork]) },
      });
    },
  });

  const handleArtworkUpload = (e) => {
    e.preventDefault();

    uploadArtwork({
      variables: {
        title,
        artist: artistName,
        medium: artMedium,
        price: artPrice,
        image: artImage,
      },
    });

    setTitle('');
    setArtistName('');
    setArtMedium('');
    setArtPrice('');
    setArtImage('');
  };

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <div>
      <h1>Virtual Art Gallery</h1>
      <div className="filters">
        <select value={artist} onChange={(e) => setArtist(e.target.value)}>
          {FILTER_OPTIONS.map((option) => (
            <option key={option.value} value={option.value}>
              {option.label}
            </option>
          ))}
        </select>
        {/* Add more filters */}
      </div>
      <form onSubmit={handleArtworkUpload}>
        <h2>Upload Artwork</h2>
        <div>
          <label>Title:</label>
          <input
            type="text"
            value={title}
            onChange={(e) => setTitle(e.target.value)}
          />
        </div>
        <div>
          <label>Artist Name:</label>
          <input
            type="text"
            value={artistName}
            onChange={(e) => setArtistName(e.target.value)}
          />
        </div>
        <div>
          <label>Medium:</label>
          <input
            type="text"
            value={artMedium}
            onChange={(e) => setArtMedium(e.target.value)}
          />
        </div>
        <div>
          <label>Price:</label>
          <input
            type="text"
            value={artPrice}
            onChange={(e) => setArtPrice(e.target.value)}
          />
        </div>
        <div>
          <label>Image URL:</label>
          <input
            type="text"
            value={artImage}
            onChange={(e) => setArtImage(e.target.value)}
          />
        </div>
        <button type="submit">Upload</button>
      </form>
      <div className="artworks">
        {data.artworks.map((artwork) => (
          <div key={artwork.id} className="artwork">
            <img src={artwork.image} alt={artwork.title} />
            <h2>{artwork.title}</h2>
            <p>{artwork.artist}</p>
            <p>{artwork.medium}</p>
            <p>{artwork.price}</p>
          </div>
        ))}
      </div>
    </div>
  );
};

export default ArtGallery;

In this updated code, we define a new mutation called "UPLOAD_ARTWORK" that accepts the title, artist, medium, price, and image fields. The mutation returns the uploaded artwork data.

We use the useMutation hook from Apollo Client to execute the mutation and update the cache with the newly uploaded artwork. The cache update ensures that the artwork is immediately displayed in the virtual art gallery without the need for a full refresh.

We add a form element for the artwork upload, including input fields for the title, artist name, medium, price, and image URL. The form's onSubmit event triggers the handleArtworkUpload function, which calls the uploadArtwork mutation with the input field values. After the upload is complete, the input fields are cleared.

Now artists can upload their artwork using the upload form, and the artwork will be displayed in the virtual art gallery.

With the React.js front-end in place, users can explore the virtual art gallery and view the artwork.

The virtual art gallery displays the artwork in a grid format, with each artwork card showing the image, title, artist name, medium, and price.

Users can also apply filters to narrow down their search. Currently, the available filter is the artist filter, which allows users to select a specific artist to view their artwork. Additional filters such as medium and price range can be added as needed.

As users select different filter options, the artwork data is refetched from the GraphQL server with the selected filters applied, providing a dynamic and personalized browsing experience.

The Role of GraphQL in Filtering and Searching Artwork

GraphQL plays a crucial role in enabling users to filter and search for artwork in the virtual art gallery application.

With GraphQL, we define a single query that retrieves the artwork data from the server. The query accepts optional filter parameters such as artist, medium, and price range.

When the user applies a filter in the React.js front-end, the corresponding filter value is passed to the GraphQL query variables. The query is then executed, and the server returns the filtered artwork data based on the provided filter values.

Related Article: How To Downgrade To A Previous Node Version In Nodejs

MongoDB's Role in Storing Artwork Data

MongoDB plays a crucial role in storing and retrieving the artwork data in the virtual art gallery application.

In MongoDB, we create a collection called "artworks" to store the artwork documents. Each document represents a piece of artwork and contains fields such as the title, artist name, medium, price, and image URL.

When an artist uploads their artwork, a new document is inserted into the "artworks" collection using the MongoDB insertOne method. The artwork data is stored in a structured format that can be easily queried and retrieved.

When a user explores the virtual art gallery, the React.js front-end sends a GraphQL query to the server, which in turn retrieves the artwork data from the MongoDB database. The server uses the MongoDB find method to filter the artwork documents based on the provided filter criteria and returns the matching documents to the front-end.

MongoDB's flexible schema allows us to store artwork data with varying fields and structures. This flexibility is particularly useful in an art gallery application where artwork can have different attributes and metadata.

Leveraging Docker for Easy Deployment and Scaling

Docker provides a streamlined solution for deploying and scaling the virtual art gallery application.

Docker also simplifies the process of scaling our application. With Docker, we can run multiple instances of the virtual art gallery container, either on a single machine or distributed across multiple machines. This allows us to handle increased traffic and load by distributing the workload among multiple containers.

Additionally, Docker provides isolation between containers, which helps prevent issues caused by conflicting dependencies or configurations. Each container runs in its own isolated environment, ensuring that the virtual art gallery application remains stable and reliable.

Additional Resources



- Introduction to GraphQL

- GraphQL documentation

- Filtering and Searching with GraphQL

You May Also Like

How to Fix the “getaddrinfo ENOTFOUND” Error in Node.js

Simple steps to resolve the getaddrinfo ENOTFOUND error in Node.js. Check the hostname or domain name, verify network connectivity, check DNS configu… read more

How To Update Node.Js

Node.js is an essential tool for many developers, and keeping it up to date is crucial for a smooth development process. In this article, you will le… read more

How to Solve ‘Cannot Find Module’ Error in Node.js

This article provides a clear guide for resolving the 'Cannot Find Module' error in Node.js. It covers possible solutions such as checking the module… read more

Advanced DB Queries with Nodejs, Sequelize & Knex.js

Learn how to set up advanced database queries and optimize indexing in MySQL, PostgreSQL, MongoDB, and Elasticsearch using JavaScript and Node.js. Di… read more

How To Check Node.Js Version On Command Line

Checking the Node.js version on the command line is an essential skill for any Node.js developer. In this article, you will learn various methods to … read more

Building a Language Learning Game with GraphQL & Nodejs

This tutorial teaches readers how to create a language learning game using GraphQL, Node.js, React.js, MongoDB, and Docker. The article covers topics… read more

How to Install a Specific Version of an NPM Package

Installing a specific version of an NPM package in a Node.js environment can be a process. By following a few steps, you can ensure that you have the… read more

How to Git Ignore Node Modules Folder Globally

Setting up Git to ignore node_modules folders globally can greatly simplify your development workflow. This article provides a simple guide on how to… read more

How to Fix the “ECONNRESET error” in Node.js

Resolving the ECONNRESET error in Node.js applications can be a challenging task. This article provides a guide on how to fix this error, focusing on… read more

How to Use Force and Legacy Peer Deps in Npm

A simple guide on using force and legacy peer deps features in Npm within Node.js context. Learn how to utilize the force flag and the legacy peer de… read more