In this section, we will walk through the process of building a full-stack application using GraphQL. This will involve setting up both the backend and frontend, connecting them, and ensuring they work seamlessly together.

Objectives

  • Understand the architecture of a full-stack application using GraphQL.
  • Set up a GraphQL server with a database.
  • Create a frontend application that interacts with the GraphQL server.
  • Implement CRUD operations in the full-stack application.

Prerequisites

  • Basic understanding of GraphQL concepts.
  • Familiarity with JavaScript/TypeScript.
  • Basic knowledge of frontend frameworks (React, Vue, or Angular).

Step-by-Step Guide

  1. Setting Up the Backend

1.1 Initialize the Project

First, create a new directory for your project and initialize a Node.js project.

mkdir fullstack-graphql
cd fullstack-graphql
npm init -y

1.2 Install Dependencies

Install the necessary dependencies for setting up a GraphQL server.

npm install express express-graphql graphql mongoose

1.3 Create the Server

Create a file named server.js and set up a basic Express server with GraphQL.

const express = require('express');
const { graphqlHTTP } = require('express-graphql');
const { buildSchema } = require('graphql');
const mongoose = require('mongoose');

// Connect to MongoDB
mongoose.connect('mongodb://localhost:27017/fullstack', { useNewUrlParser: true, useUnifiedTopology: true });

// Define a schema
const schema = buildSchema(`
  type Query {
    hello: String
  }
`);

// Define a root resolver
const root = {
  hello: () => 'Hello world!',
};

// Create an Express app
const app = express();

// Use graphqlHTTP middleware
app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: root,
  graphiql: true,
}));

// Start the server
app.listen(4000, () => {
  console.log('Server is running on http://localhost:4000/graphql');
});

1.4 Define the Schema and Resolvers

Expand the schema to include more complex types and resolvers.

const schema = buildSchema(`
  type User {
    id: ID!
    name: String!
    email: String!
  }

  type Query {
    users: [User]
    user(id: ID!): User
  }

  type Mutation {
    createUser(name: String!, email: String!): User
  }
`);

const User = mongoose.model('User', new mongoose.Schema({
  name: String,
  email: String,
}));

const root = {
  users: () => User.find(),
  user: ({ id }) => User.findById(id),
  createUser: ({ name, email }) => {
    const user = new User({ name, email });
    return user.save();
  },
};

  1. Setting Up the Frontend

2.1 Initialize the Frontend Project

Create a new directory for the frontend and initialize a React project.

npx create-react-app frontend
cd frontend

2.2 Install Apollo Client

Install Apollo Client to interact with the GraphQL server.

npm install @apollo/client graphql

2.3 Set Up Apollo Client

Create a file named ApolloClient.js to configure Apollo Client.

import { ApolloClient, InMemoryCache, ApolloProvider } from '@apollo/client';

const client = new ApolloClient({
  uri: 'http://localhost:4000/graphql',
  cache: new InMemoryCache()
});

export default client;

2.4 Create React Components

Create components to fetch and display data from the GraphQL server.

// src/App.js
import React from 'react';
import { ApolloProvider } from '@apollo/client';
import client from './ApolloClient';
import Users from './Users';

function App() {
  return (
    <ApolloProvider client={client}>
      <div className="App">
        <h1>Users</h1>
        <Users />
      </div>
    </ApolloProvider>
  );
}

export default App;

// src/Users.js
import React from 'react';
import { useQuery, gql } from '@apollo/client';

const GET_USERS = gql`
  query GetUsers {
    users {
      id
      name
      email
    }
  }
`;

function Users() {
  const { loading, error, data } = useQuery(GET_USERS);

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

  return (
    <ul>
      {data.users.map(user => (
        <li key={user.id}>
          {user.name} - {user.email}
        </li>
      ))}
    </ul>
  );
}

export default Users;

  1. Implementing CRUD Operations

3.1 Create User Component

Add a form to create new users.

// src/CreateUser.js
import React, { useState } from 'react';
import { useMutation, gql } from '@apollo/client';

const CREATE_USER = gql`
  mutation CreateUser($name: String!, $email: String!) {
    createUser(name: $name, email: $email) {
      id
      name
      email
    }
  }
`;

function CreateUser() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [createUser] = useMutation(CREATE_USER);

  const handleSubmit = (e) => {
    e.preventDefault();
    createUser({ variables: { name, email } });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        placeholder="Name"
        value={name}
        onChange={(e) => setName(e.target.value)}
      />
      <input
        type="email"
        placeholder="Email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
      />
      <button type="submit">Create User</button>
    </form>
  );
}

export default CreateUser;

3.2 Integrate CreateUser Component

Integrate the CreateUser component into the main application.

// src/App.js
import React from 'react';
import { ApolloProvider } from '@apollo/client';
import client from './ApolloClient';
import Users from './Users';
import CreateUser from './CreateUser';

function App() {
  return (
    <ApolloProvider client={client}>
      <div className="App">
        <h1>Users</h1>
        <CreateUser />
        <Users />
      </div>
    </ApolloProvider>
  );
}

export default App;

  1. Running the Application

4.1 Start the Backend Server

Run the backend server.

node server.js

4.2 Start the Frontend Application

Run the frontend application.

npm start

  1. Conclusion

In this section, we have built a full-stack application using GraphQL. We set up a GraphQL server with a MongoDB database, created a React frontend to interact with the server, and implemented basic CRUD operations. This foundational knowledge can be expanded to build more complex and feature-rich applications.

Exercises

  1. Add Update and Delete Operations:

    • Extend the GraphQL schema to include mutations for updating and deleting users.
    • Implement the corresponding resolvers.
    • Create React components to handle these operations.
  2. Pagination:

    • Implement pagination in the Users component to handle large datasets.
  3. Authentication:

    • Add authentication to the GraphQL server and protect certain queries and mutations.

Solutions

  1. Update and Delete Operations:

    Schema:

    type Mutation {
      createUser(name: String!, email: String!): User
      updateUser(id: ID!, name: String, email: String): User
      deleteUser(id: ID!): User
    }
    

    Resolvers:

    const root = {
      users: () => User.find(),
      user: ({ id }) => User.findById(id),
      createUser: ({ name, email }) => {
        const user = new User({ name, email });
        return user.save();
      },
      updateUser: ({ id, name, email }) => User.findByIdAndUpdate(id, { name, email }, { new: true }),
      deleteUser: ({ id }) => User.findByIdAndRemove(id),
    };
    

    React Components:

    • Create similar components for UpdateUser and DeleteUser as shown for CreateUser.
  2. Pagination:

    • Modify the GET_USERS query to accept pagination parameters and update the Users component to handle pagination.
  3. Authentication:

    • Use libraries like jsonwebtoken to add authentication middleware to the GraphQL server and protect specific queries and mutations.

By completing these exercises, you will gain a deeper understanding of building full-stack applications with GraphQL and be better prepared to handle real-world scenarios.

© Copyright 2024. All rights reserved