Back to Gqlgen

📝 mini-habr-API

_examples/mini-habr-with-subscriptions/README.md

0.17.9010.9 KB
Original Source

📝 mini-habr-API

This project demonstrates how to implement GraphQL subscriptions and cursor-based pagination using gqlgen in a mini API similar to Habr. The implementation follows the official GraphQL documentation specifications and showcases real-time data exchange and efficient data fetching patterns.

Key GraphQL Features Demonstrated

GraphQL Subscriptions

The project provides a complete implementation of GraphQL subscriptions using WebSockets, allowing clients to receive real-time updates when new comments are added to posts. This follows the GraphQL subscription specification and shows how to:

  • Set up subscription resolvers in gqlgen
  • Manage WebSocket connections efficiently
  • Implement the publish-subscribe pattern for real-time updates
  • Handle connection lifecycle and cleanup

Cursor-based Pagination

Following GraphQL's Relay Cursor Connections Specification, this project implements efficient cursor-based pagination for comments on posts. This approach:

  • Provides stable pagination that works reliably with changing datasets
  • Enables clients to navigate large result sets efficiently
  • Implements proper pageInfo with hasNextPage and cursor management
  • Demonstrates how to structure connection types in gqlgen schemas

This project implements an API for Ozon similar to Habr. It allows working with posts and comments using GraphQL. The system supports creating posts, adding comments, managing comment enabling/disabling for posts, as well as subscribing to new comment notifications.

📡 Subscription System (WebSockets)12:

  • Publish-Subscribe Pattern: Implementation of Pub/Sub for real-time notifications about new comments, where components interact through a central channel mechanism
  • Thread-safe subscription management: Using mutexes for safe access to the subscriber list in a concurrent environment
  • Automatic resource cleanup: Proper closing of channels and removal of inactive subscribers to prevent memory leaks
  • Asynchrony: Using non-blocking Go channels for data transmission
  • Error resistance: Protection against panics when sending data to closed channels using deferred functions
  • Scalability: Ability to subscribe to events by specific post identifier, providing targeted notification delivery

Note on patterns: Unlike the classic Observer pattern, where observers directly register with the observed object, this project implements the Publish-Subscribe pattern, which introduces an intermediate layer (message broker) between publishers and subscribers. This provides a higher degree of decomposition: publishers don't know about specific subscribers, and subscribers don't know about publishers. Subscriptions are grouped by post identifier, which allows implementing event filtering at the broker level.

🚀 Project Launch

To launch the project, follow these steps:

Prerequisites

  • Docker and Docker Compose installed on your system
  • Git for cloning the repository

Installation Steps

  1. Clone the repository

    bash
    git clone https://github.com/nabishec/ozon_habr_api.git
    cd ozon_habr_api
    
  2. Launch in Docker containers

    bash
    docker-compose up
    
  3. Using the API

    After launching, open your browser and go to:

    http://localhost:8080
    

Choosing Data Storage

By default, the project uses PostgreSQL for data storage. If you want to use in-memory storage for testing, change the line in the Dockerfile:

RUN go build -o main ./cmd/main.go

and add the flag -s m:

RUN go build -o main ./cmd/main.go -s m

📖 API Documentation

Interactive GraphQL playground console is available at:

GraphQL Query Examples

<details> <summary><b>Getting a list of all posts</b></summary>
query{
    posts{
        id
        title
        text
        authorID
        commentsEnabled
        createDate
    }
}
</details> <details> <summary><b>Getting a specific post with comments</b></summary>
query {
    post(postID: 1) {
        id
        title
        text
        comments(first: 5) {
            edges {
                node {
                    id
                    text
                    authorID
                    createDate
                }
                cursor
            }
            pageInfo {
                hasNextPage
                endCursor
            }
        }
    }
}
</details> <details> <summary><b>Creating a new post</b></summary>
mutation {
    addPost(postInput: {
        authorID: "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"
        title: "New post"
        text: "Post content"
        commentsEnabled: true
    }) {
        id
        title
        createDate
    }
}
</details> <details> <summary><b>Creating a new comment</b></summary>
mutation {
    addComment(commentInput: {
        authorID: "123e4567-e89b-12d3-a456-426614174000",
        postID: 1,
        parentID: 1, # ID of existing comment
        text: "This is a reply to comment 1"
    }) {
        id
        text
        parentID
    }
}
</details> <details> <summary><b>Subscribing to new comments</b></summary>
subscription {
    commentAdded(postID: 1) {
        id
        text
        authorID
        createDate
    }
}
</details>

For testing the API, you can use any GraphQL clients, such as Insomnia, Postman, or GraphiQL.

📁 Project Structure

<details> <summary style="display: inline-flex; align-items: center;"> <b>Show structure </b> </summary>

ozon_habr_api/

├── cmd/

│ ├── db_connection/

│ │ ├── cache.go (Redis connection and configuration for caching)

│ │ └── database.go (PostgreSQL connection and configuration)

│ ├── server/

│ │ └── server.go (GraphQL server setup and launch)

│ └── main.go (Main entry point, application setup and launch)

├── graph/

│ ├── model/

│ │ └── models_gen.go (Automatically generated GraphQL models)

│ ├── generated.go (Generated GraphQL code (gqlgen))

│ ├── resolver.go (Main GraphQL resolvers)

│ ├── schema.graphqls (GraphQL schema definition)

│ ├── schema.resolvers.go (GraphQL resolvers implementation)

│ └── subscription.go (Implementation of structures and methods for subscription management)

├── internal/

│ ├── handlers/

│ │ ├── comment_mutation/ (Comment mutations logic handlers)

│ │ │ ├── interface.go (Interface for comment mutations)

│ │ │ └── mutations.go (Comment mutations implementation)

│ │ ├── comment_query/ (Comment queries logic handlers)

│ │ │ ├── interface.go (Interface for comment queries)

│ │ │ └── query.go (Comment queries implementation)

│ │ ├── post_mutation/ (Post mutations logic handlers)

│ │ │ ├── interface.go (Interface for post mutations)

│ │ │ └── mutations.go (Post mutations implementation)

│ │ └── post_query/ (Post queries logic handlers)

│ │ ├── interface.go (Interface for post queries)

│ │ └── query.go (Post queries implementation)

│ ├── pkg/

│ │ ├── cursor/

│ │ | └── cursor.go (Functions for working with cursors in pagination)

│ │ └── errs/

│ │ └── errors.go (Stores business logic errors)

│ ├── model/

│ │ └── model.go (Internal data models)

│ └── storage/

│ ├── db/ (Implementation of database storage)

│ │ └── resolvers.go (Implementation of methods for working with PostgreSQL database)

│ ├── in-memory/ (Implementation of in-memory data storage)

│ │ └── resolvers.go (Implementation of methods for working with in-memory data)

│ └── interface.go (Interface for data storage (PostgreSQL, in-memory))

├── migrations/

│ └── 001_create_tables.up.sql (SQL script for database migration (creating tables))

├── .env (Environment variables file (database settings, Redis, etc.))

├── .gitignore (List of ignored files and directories for Git)

├── docker-compose.yml (Docker Compose configuration for launching the application and dependencies)

├── Dockerfile (Instructions for building Docker image)

├── go.mod (Go dependencies file)

├── go.sum (Go dependencies checksums file)

├── gqlgen.yml (Configuration file for gqlgen)

├── LICENSE (Project license)

└── README.md (Project description file)

</details>

🔧 Stack:

  • Go 1.25
  • GraphQL (gqlgen)
  • PostgreSQL 17
  • Redis 9
  • Docker & Docker Compose