Table of Contents
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
Building a React.js Front-end for the Virtual Art Gallery
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.
Uploading Artwork in the Virtual Art Gallery Application
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.
Exploring the Gallery with React.js Front-end
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