Building a Full-Stack GraphQL Application: A Guided Project with @thenetninjauk

Building a Full-Stack GraphQL Application: A Guided Project with @thenetninjauk

Discover how to build a full-stack application using GraphQL, Node.js, Express, MongoDB Atlas, and React.js

Introduction

In this blog, inspired by the tutorials from the YouTube channel "https://youtu.be/Y0lDGjwRYKw" we will dive into the exciting world of GraphQL and explore how it can be used to build a book info site. This project, developed while following along with the educational content on the channel, combines a GraphQL server on Node.js, a React front-end powered by Apollo Client, and MongoDB as the database to store book information. Let's take a closer look at the features and functionalities of this book info site.

Overview of the Project:

The book info site serves as a platform where users can add and view information about books. The process begins with users adding a new book through an intuitive interface. When a book is added, its details, such as the title, are stored in the MongoDB Atlas database. Subsequently, the newly added book title is dynamically displayed on the site, allowing users to easily browse through the collection of books.

Upon clicking on a book title, the site retrieves additional information about the selected book from the MongoDB Atlas database. This information includes details such as the author's name, publication date, genre, and other relevant information. The retrieved book details are then displayed alongside the book list, providing users with comprehensive insights into each book's attributes.

Technology Stack:

The book info site harnesses the power of several cutting-edge technologies:

  1. GraphQL Server on Node.js:

    • Utilizing GraphQL as the query language, the Node.js server handles incoming requests from the front end and interacts with the database accordingly.

    • GraphQL's flexible nature allows clients to precisely specify the data they require, minimizing over-fetching and improving efficiency.

  2. React Front-end with Apollo Client:

    • The front end of the book info site is built using React, a popular JavaScript library for building user interfaces.

    • Apollo Client is employed as the GraphQL client, facilitating seamless communication between the front end and the GraphQL server.

    • With Apollo Client, developers can easily define and execute queries to fetch and mutate data, providing a smooth user experience.

  3. MongoDB for Data Storage:

    • The book info site leverages MongoDB, a NoSQL document database, to store and retrieve book information.

    • MongoDB Atlas, a cloud-based database service, is employed to ensure reliable data storage and easy scalability.

    • The site interacts with the MongoDB Atlas database through the GraphQL server, enabling efficient data retrieval and updates.

Project Structure

I started by developing a GraphQL server in Node.js using Express. After setting up the server, the next step was to establish a connection between the backend and the MongoDB Atlas database.

With the backend in place, my focus shifted to the frontend development. I built a client-side application using React, which allowed me to make queries to our GraphQL server. These queries were designed to fetch data from the database, and the retrieved data was then displayed in the user's browser.

By combining the GraphQL server, backend database integration, and React frontend, I created a robust and efficient application stack. This approach enabled seamless communication between the client and server, providing users with a smooth and interactive experience while retrieving and displaying data from the MongoDB Atlas database.

Understanding Server side (Backend):

To set up the server-side (backend) of my application, I created two folders within the root directory: one for the client-side code and the other for the server-side code. In the server folder, I began by setting up an Express application in app.js. For working with GraphQL, I installed two npm packages: graphql and express-graphql. The graphql package provides the JavaScript implementation of GraphQL, while express-graphql allows our Express application to understand and interact with GraphQL since Express doesn't natively support GraphQL.

Here's an overview of the code in app.js:

const express = require ('express')
const graphqlHTTP = require('express-graphql')
const schema = require('./schema/schema')
const mongoose = require('mongoose')
const cors = require('cors');


mongoose.connect('<connection string>')
mongoose.connection.once('open', () => {
    console.log("connected to database")
})

const app = express();

// allow cross origin request 
app.use(cors());

app.use('/graphql', graphqlHTTP.graphqlHTTP({
    schema,  //this schema we are referring to defines the graph and relation of data
    graphiql: true
}));

app.listen(3001, () =>{
    console.log("listening request on port 3001");
});

After establishing the server, I connected it to the MongoDB database using mongoose.connect() and ensured that the connection was successful. I then created an Express app instance and enabled cross-origin requests using cors().

To integrate GraphQL with the Express app, I used graphqlHTTP middleware and passed in the schema object, which defines the structure and relationships of the data graph. Additionally, I set graphiql to true to enable the GraphiQL interface, allowing developers to test and explore the GraphQL API interactively.

Finally, I listened for incoming requests on port 3001 using app.listen().

This setup allows the server to handle GraphQL queries and mutations via the /graphql endpoint, providing a powerful and flexible backend for the client-side React application.

graphqlHTTP is a function provided by express-graphql. It is used to create a middleware function that can handle GraphQL requests. we use it as a middleware on a single route and this route will act like an endpoints to interact with the graphql data.

The app.use('/graphql', graphqlHTTP.graphqlHTTP()) middleware is a crucial part of our setup. Whenever the /graphql route is accessed, this middleware function is triggered. Its purpose is to handle GraphQL requests.

Within the graphqlHTTP.graphqlHTTP() function, we pass in an options object. One of the options is the schema, which refers to the schema that defines the structure and relationships of the data graph in our application. The schema acts as a blueprint for the GraphQL server to understand and validate the incoming queries.

Additionally, we set graphiql: true in the options, enabling the GraphiQL interface. This interface provides a user-friendly environment to interact with and test our GraphQL API.

To summarize, the graphqlHTTP.graphqlHTTP() function, used as middleware, plays a crucial role in handling GraphQL requests. It utilizes the specified schema and provides an interactive playground through the GraphiQL interface.

Defining GraphQL Object Types and Schema Structure

In the code snippet below, we define a GraphQL object type called "Book." This type represents the structure and fields that define a book within our GraphQL schema.

const BookType = new GraphQLObjectType({
    name: "Book",
    fields: () => ({
        id: { type: GraphQLID },
        name: { type: GraphQLString },
        genre: { type: GraphQLString },
        author: {
            type: AuthorType,
            resolve(parent, args) {
                // Code for resolving the author information
            }
        }
    })
})

The BookType object is created using the GraphQLObjectType constructor provided by the GraphQL package. This constructor takes in an object that specifies various properties of the object type.

In this case, we define the name property as "Book" to identify this object type in our schema. The fields property is a function that returns an object containing the fields of the Book object type.

For the Book object type, we include fields such as id, name, genre, and author. Each field has a specified type indicating the data type of the field (e.g., GraphQLID and GraphQLString).

The author field has a nested object type, AuthorType, which represents the author associated with the book. The resolve function within the author field specifies how to resolve the author information. In this example, it demonstrates two possibilities: finding the author from dummy data (_.find(authors, {id: parent.authorId})) or querying the database using Author.findById(parent.authorId).

Overall, the schema defines the structure, relationships, and available fields of our GraphQL API. It consists of object types like BookType, along with other object types, root queries, and mutations that collectively define the entire data graph of our application. So thats the Schema which is defined for more details you can see the video tutorial by The Net Ninja.

Connecting to MongoDB Atlas and Creating Mongoose Models

Connecting to MongoDB Atlas and setting up the Mongoose models for data storage is an essential part of the backend development process. Here's a breakdown of the steps involved:

  1. Setting up MongoDB Atlas: To connect our website to MongoDB, we first need to create a cluster on MongoDB Atlas. The process of setting up the cluster is detailed on their website, and you can refer to the provided documentation for more information.

  2. Connecting to MongoDB Atlas: To establish the connection with MongoDB Atlas, we utilize the Mongoose library in our app.js file. We use the connection string provided by MongoDB Atlas to connect to the online database. Here's an example of the code:

mongoose.connect('<connection string>');
mongoose.connection.once('open', () => {
    console.log("Connected to the database");
});
  1. Defining Mongoose Models and Schemas: Before storing data in MongoDB, we need to define the data types or schemas that represent our collections. In the model folder, we create separate files for each model. For example, let's consider the Book and Author models. In Book.js, we define the schema for the book data as follows:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;

const bookSchema = new Schema({
    title: String,
    genre: String,
    authorId: String
});

module.exports = mongoose.model("Book", bookSchema);

Similarly, in Author.js, we define the schema for the author data:

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

const authorSchema = new Schema({
    name: String,
    age: Number
});

module.exports = mongoose.model("Author", authorSchema);
  1. Testing with GraphiQL: After setting up the backend, schemas, and models, we can test our GraphQL API using GraphiQL. GraphiQL provides a user-friendly interface to write and execute GraphQL queries, inspect the responses, and explore the API's schema. It allows us to ensure that our backend is functioning correctly and that the data is being stored and retrieved accurately.

By completing these steps, we establish the connection to the MongoDB Atlas database and define the schemas for our data using Mongoose models. With these in place, we are ready to store and retrieve data from the MongoDB database, completing the backend part of our application.

Understanding Client (Frontend):

The frontend of our application is built using React.js. However, since GraphQL Query Language is not directly understood by React, we need a GraphQL client to connect our application with GraphQL. In our case, we utilize Apollo Client as the GraphQL client.

Typically, in a React-based frontend, we create functional components to render different parts of our application. For our scenario, we have two main components: BookList and AddBook.

The BookList component is responsible for rendering the books retrieved from the database. On the other hand, the AddBook component allows users to add new books to the database. Both of these components make queries to the server to fetch or modify data.

To handle these queries, we utilize Apollo Client. The queries are defined in a separate file using the Apollo Client library. The client is then integrated into the components, and when the components render in the browser, Apollo Client automatically handles the queries to the server and returns any data received.

If you would like to see the frontend implementation in detail, you can refer to my GitHub repository: Niranjangkr/GraphQL_Project. You can explore the code and leverage it as a reference for your own project.

For a more comprehensive understanding, I recommend checking out the GraphQL course by The Net Ninja. It provides detailed explanations and examples. Although the tutorial may use class components, I opted for functional components in my implementation to simplify the development process.

Conclusion

In conclusion, this blog post demonstrated the process of building a full-stack application using GraphQL, Node.js, Express, MongoDB Atlas, and React.js. By following the tutorial provided by The Net Ninja, developers were able to understand and implement GraphQL concepts, connect to a MongoDB database, define schemas, and build a frontend using React and Apollo Client. The resources and guidance offered in the tutorial were invaluable in successfully completing the project.

Did you find this article valuable?

Support Niranjan Gaonkar by becoming a sponsor. Any amount is appreciated!