Skip to content

GraphQL Project — Build a Complete Full-Stack Application

DodaTech Updated 2026-06-28 7 min read

In this tutorial, you will learn about GraphQL Project. We cover key concepts, practical examples, and best practices to help you master this topic.

This project walks through building a complete full-stack GraphQL application — a Durga Antivirus Pro dashboard with schema design, resolvers, React frontend, and deployment.

What You'll Learn

You will build a production-ready GraphQL application from scratch: design the schema, implement Apollo Server with authentication and subscriptions, build a React dashboard with Apollo Client, and deploy to production.

Why This Project Matters

Theory is important, but building a complete GraphQL application ties everything together — schema design, resolvers, authentication, caching, subscriptions, testing, and deployment. DodaTech's engineering team built the Durga Antivirus Pro dashboard as a GraphQL application, and this project mirrors the real architecture.

flowchart TB
    A["React Dashboard\n(Apollo Client)"] --> B["Apollo Server\n(GraphQL API)"]
    B --> C["SQL Database\n(Users, Devices)"]
    B --> D["Redis\n(Cache, Subscriptions)"]
    B --> E["External APIs\n(Threat Intel)"]
    A --> F["WebSocket\n(Subscriptions)"]
    F --> B
    style A fill:#dbeafe,stroke:#2563eb
    style B fill:#fef3c7,stroke:#d97706
â„šī¸ Info

Prerequisites: All previous GraphQL tutorials. Node.js and React experience.

Project Structure

dodatech-graphql/
  server/
    src/
      schema/
        typeDefs.graphql
        resolvers.js
      context.js
      loaders.js
      server.js
    package.json
  client/
    src/
      components/
        Dashboard.jsx
        ThreatList.jsx
        DeviceList.jsx
      hooks/
        useQueries.js
      App.jsx
    package.json

Step 1: Schema Design

# server/src/schema/typeDefs.graphql
enum Severity { LOW MEDIUM HIGH CRITICAL }
enum DeviceStatus { ONLINE OFFLINE QUARANTINED }
enum UserRole { ADMIN ANALYST VIEWER }

type User {
  id: ID!
  email: String!
  displayName: String!
  role: UserRole!
  devices: [Device!]!
  createdAt: DateTime!
}

type Device {
  id: ID!
  name: String!
  os: String!
  status: DeviceStatus!
  user: User!
  threats: [Threat!]!
  scans: [Scan!]!
  lastScan: DateTime
}

type Threat {
  id: ID!
  name: String!
  severity: Severity!
  detectedAt: DateTime!
  resolvedAt: DateTime
  device: Device!
}

type Scan {
  id: ID!
  device: Device!
  status: ScanStatus!
  threatsFound: Int!
  startedAt: DateTime!
  completedAt: DateTime
}

enum ScanStatus { PENDING RUNNING COMPLETED FAILED }

type Query {
  me: User
  devices(status: DeviceStatus): [Device!]!
  device(id: ID!): Device
  threats(severity: Severity, limit: Int = 20): [Threat!]!
  threat(id: ID!): Threat
  search(query: String!): [SearchResult!]!
}

union SearchResult = Device | Threat | User

input CreateDeviceInput { name: String! os: String! }
input UpdateDeviceInput { name: String os: String status: DeviceStatus }

type Mutation {
  login(email: String!, password: String!): AuthPayload!
  createDevice(input: CreateDeviceInput!): Device!
  updateDevice(id: ID!, input: UpdateDeviceInput!): Device!
  deleteDevice(id: ID!): Boolean!
  reportThreat(name: String!, severity: Severity!, deviceId: ID!): Threat!
  resolveThreat(id: ID!): Threat!
  startScan(deviceId: ID!): Scan!
}

type AuthPayload {
  token: String!
  user: User!
}

type Subscription {
  threatDetected(deviceId: ID): Threat!
  scanCompleted(deviceId: ID): Scan!
  deviceStatusChanged: Device!
}

Step 2: Apollo Server Implementation

// server/src/server.js
const { ApolloServer } = require('apollo-server');
const { loadFilesSync } = require('@graphql-tools/load-files');
const { mergeTypeDefs } = require('@graphql-tools/merge');

const typeDefs = mergeTypeDefs(loadFilesSync('src/schema/*.graphql'));
const resolvers = require('./resolvers');
const { createContext } = require('./context');
const { authDirectiveTransformer } = require('./directives/auth');

let schema = makeExecutableSchema({ typeDefs, resolvers });
schema = authDirectiveTransformer(schema);

const server = new ApolloServer({
  schema,
  context: createContext,
  csrfPrevention: true,
  introspection: process.env.NODE_ENV !== 'production',
  cache: 'bounded',
  plugins: [
    responseCachePlugin(),
  ],
  validationRules: [
    depthLimit(7),
    costAnalysis({ maximumCost: 1000, defaultCost: 1 }),
  ],
});

server.listen(4000).then(({ url }) => {
  console.log(`DodaTech GraphQL API at ${url}`);
});

Step 3: React Client with Apollo

// client/src/App.jsx
import { ApolloClient, InMemoryCache, ApolloProvider } from '@apollo/client';
import { setContext } from '@apollo/client/link/context';
import { createHttpLink } from '@apollo/client/link/http';
import { WebSocketLink } from '@apollo/client/link/ws';
import { split } from '@apollo/client/link';
import { getMainDefinition } from '@apollo/client/utilities';
import { Dashboard } from './components/Dashboard';

const httpLink = createHttpLink({ uri: 'http://localhost:4000/graphql' });

const authLink = setContext((_, { headers }) => {
  const token = localStorage.getItem('token');
  return {
    headers: { ...headers, authorization: token ? `Bearer ${token}` : '' },
  };
});

const wsLink = new WebSocketLink({
  uri: 'ws://localhost:4000/graphql',
  options: {
    reconnect: true,
    connectionParams: { authToken: localStorage.getItem('token') },
  },
});

const link = split(
  ({ query }) => {
    const def = getMainDefinition(query);
    return def.kind === 'OperationDefinition' && def.operation === 'subscription';
  },
  wsLink,
  authLink.concat(httpLink)
);

const client = new ApolloClient({ link, cache: new InMemoryCache() });

function App() {
  return (
    <ApolloProvider client={client}>
      <Dashboard />
    </ApolloProvider>
  );
}
// client/src/components/Dashboard.jsx
import { useQuery, gql } from '@apollo/client';

const DASHBOARD_QUERY = gql`
  query Dashboard {
    me { id displayName email role }
    devices { id name os status }
    threats(severity: CRITICAL, limit: 10) {
      id name severity detectedAt device { id name }
    }
  }
`;

function Dashboard() {
  const { loading, error, data } = useQuery(DASHBOARD_QUERY);
  
  if (loading) return <LoadingSpinner />;
  if (error) return <ErrorBanner error={error} />;
  
  return (
    <div className="dashboard">
      <UserProfile user={data.me} />
      <DeviceGrid devices={data.devices} />
      <ThreatFeed threats={data.threats} />
      <ScanProgress subscription />
    </div>
  );
}

Step 4: Real-Time Subscriptions

const THREAT_SUBSCRIPTION = gql`
  subscription OnThreatDetected($deviceId: ID) {
    threatDetected(deviceId: $deviceId) {
      id name severity detectedAt device { id name }
    }
  }
`;

function ThreatFeed({ threats }) {
  const { data: subscriptionData } = useSubscription(
    THREAT_SUBSCRIPTION,
    { variables: { deviceId: selectedDeviceId } }
  );
  
  // Merge subscription updates into the feed
  const allThreats = useMemo(() => {
    const threatsList = [...threats];
    if (subscriptionData?.threatDetected) {
      threatsList.unshift(subscriptionData.threatDetected);
    }
    return threatsList.slice(0, 50);
  }, [threats, subscriptionData]);
  
  return (
    <div className="threat-feed">
      <h2>Live Threat Feed</h2>
      {allThreats.map(threat => (
        <ThreatCard key={threat.id} threat={threat} />
      ))}
    </div>
  );
}

Step 5: Testing

// server/src/__tests__/resolvers.test.js
describe('Mutation.reportThreat', () => {
  it('creates a threat and publishes subscription event', async () => {
    const mockPubsub = { publish: jest.fn() };
    const mockDevice = { id: 'dev-001', userId: 'user-001' };
    
    const result = await resolvers.Mutation.reportThreat(
      null,
      { name: 'Emotet', severity: 'CRITICAL', deviceId: 'dev-001' },
      { user: { id: 'user-001' }, db: mockDb, pubsub: mockPubsub }
    );
    
    expect(result.name).toBe('Emotet');
    expect(result.severity).toBe('CRITICAL');
    expect(mockPubsub.publish).toHaveBeenCalledWith(
      'THREAT_DETECTED',
      expect.objectContaining({ threatDetected: expect.objectContaining({ name: 'Emotet' }) })
    );
  });
});

Common Mistakes

1. Overcomplicating the Schema

Start with 3-4 core types and add more as needed. A schema with 50 types on day one is overwhelming and likely wrong.

2. Not Having a Seed Script

Without seed data, development is slow. Write a seed script that populates the database with realistic test data.

3. Ignoring Error States in the Frontend

Every query can fail. Show loading states, error states, and empty states for every component. Never assume data always exists.

4. Forgetting to Handle Subscription Cleanup

Subscriptions leak if not cleaned up on component unmount. Apollo Client handles this with useSubscription but verify cleanup in tests.

5. Deploying Without Security Hardening

Before deploying, disable introspection, add Rate Limiting, configure CORS, and set up query cost analysis. A public API without these is vulnerable.

Practice Questions

  1. What is the recommended project structure for a GraphQL application?
  2. How do you handle authentication in the frontend?
  3. How do subscriptions work in React?
  4. What should a seed script include?
  5. What security measures are needed before deploying?

Answers:

  1. /server (schema, resolvers, context, loaders) and /client (components, hooks, cache config). Keep GraphQL operations in .graphql files for codegen.
  2. Store the JWT in localStorage, create an authLink that reads it and sets the Authorization header, and pass the token in Websocket connectionParams for subscriptions.
  3. Use Apollo's useSubscription hook with the subscription query. New events merge into the reactive data. Clean up with useEffect return.
  4. A seed script creates realistic test data with proper relationships — users with devices, devices with threats and scans. It runs before tests and development.
  5. Disable introspection, enable CSRF prevention, configure CORS, add rate limiting, set query depth/cost limits, enable HTTPS, and implement authentication.

Challenge: Complete the full Durga Antivirus Pro GraphQL project. Build all resolvers with DataLoader, implement authentication with JWT, add real-time subscriptions with Redis Pub/Sub, build the React dashboard with Apollo Client, write unit and integration tests, and deploy to production with security hardening.

FAQ

Should I use GraphQL for the entire application?

GraphQL excels for complex UIs with nested data. For simple CRUD endpoints, REST may be simpler. Most applications use both — GraphQL for the frontend, REST for external APIs.

How do I deploy a GraphQL application?

Server: Docker container on cloud run. Client: Static build on CDN. Use environment variables for API URLs. Set up CI/CD with automated testing before deployment.

What database works best with GraphQL?

Any database works — PostgreSQL (most common), MongoDB (flexible schemas), or Fauna (serverless). Choose based on your data model, not your API paradigm.

How do I handle file uploads in the project?

Use the Upload scalar. On the frontend, use a file input and pass the file object to the mutation. The resolver streams the file to cloud storage.

What monitoring should I set up?

Apollo Studio for query performance, error tracking (Sentry), uptime monitoring, database query monitoring (pg_stat_statements), and custom metrics for resolver execution times.

Mini Project

Complete the Durga Antivirus Pro GraphQL project. Implement all schema types with proper resolvers and DataLoaders. Build the React frontend with dashboard, device management, threat feed, and scan controls. Add real-time subscriptions for threat alerts and scan completion. Write comprehensive tests. Deploy with security hardening.

What's Next

Topic Description
RESTful APIs Compare GraphQL with REST patterns
gRPC Guide High-performance microservice communication
GraphQL Introduction Review core GraphQL concepts
âŦ… Performance Optimization
➡ gRPC Guide

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro