What is NATS?
NATS is a lightweight, high-performance messaging system designed for cloud-native applications. Its Key-Value Store (KV) feature allows you to store and retrieve data efficiently, making it ideal for applications that require fast access to stateful data. NATS provides features like persistence, history, and multi-tenancy, which are beneficial for building scalable applications.
Project Overview
The Trivia Quiz Application allows users to:
- Create and manage trivia questions
- Take quizzes and answer trivia questions
- View scores and leaderboards
This application provides an engaging platform for users to challenge themselves and others in a fun trivia format.
Setting Up NATS with Docker
To get started with NATS, you can easily run it using Docker:
- Install Docker if you haven't already.
- Pull and run the NATS container:
docker run -p 4222:4222 -p 8222:8222 nats:latest
This command starts a NATS server that listens on port 4222 for client connections and exposes a monitoring interface on port 8222.
Database Schema
Our application will use a simple key-value structure to store trivia questions and user scores. Each question will be stored under a unique key (its ID), with its details serialized as JSON.
Example Key-Value Structure
- Key: question:
- Value: JSON object containing question, options, correct_answer
- Key: score:
- Value: JSON object containing username and score
Integrating NATS with Gofr
The integration between NATS and Gofr is straightforward. We will set up the NATS client in our application:
package main
import (
"encoding/json"
"fmt"
"gofr.dev/pkg/gofr"
"gofr.dev/pkg/gofr/datasource/kv-store/nats"
"gofr.dev/pkg/gofr/http"
)
type Question struct {
ID string `json:"id,omitempty"`
Text string `json:"question"`
Options []string `json:"options"`
CorrectAnswer string `json:"correct_answer"`
}
type Score struct {
Username string `json:"username"`
Score int `json:"score"`
}
func main() {
app := gofr.New()
app.AddKVStore(nats.New(nats.Configs{
Server: "nats://localhost:4222",
Bucket: "trivia",
}))
app.POST("/question", CreateQuestion)
app.POST("/answer", SubmitAnswer)
app.GET("/leaderboard", GetLeaderboard)
app.Run()
}
Core Data Models
We define our data structures for trivia questions and scores as follows:
type Question struct {
ID string `json:"id,omitempty"`
Text string `json:"question"`
Options []string `json:"options"`
CorrectAnswer string `json:"correct_answer"`
}
type Score struct {
Username string `json:"username"`
Score int `json:"score"`
}
API Endpoints
- Create Question (POST /question) This endpoint allows administrators to add new trivia questions:
POST http://localhost:9000/question
Content-Type: application/json
{
"question": "What is the capital of France?",
"options": ["Berlin", "Madrid", "Paris", "Rome"],
"correct_answer": "Paris"
}
- Submit Answer (POST /answer)
POST http://localhost:9000/answer
Content-Type: application/json
{
"username": "john_doe",
"question_id": "question:12345",
"selected_option": "Paris"
}
3. View Leaderboard (GET /leaderboard)
GET http://localhost:9000/leaderboard
Implementation Details
The core of our implementation lies in defining the handlers for each endpoint:
func CreateQuestion(ctx *gofr.Context) (interface{}, error) {
var question Question
if err := ctx.Bind(&question); err != nil {
return nil, http.ErrorInvalidParam{Params: []string{"body"}}
}
question.ID = uuid.New().String()
questionData, err := json.Marshal(question)
if err != nil {
return nil, fmt.Errorf("failed to serialize question")
}
if err := ctx.KVStore.Set(ctx, fmt.Sprintf("question:%s", question.ID), string(questionData)); err != nil {
return nil, err
}
return question, nil
}
func SubmitAnswer(ctx *gofr.Context) (interface{}, error) {
var request struct {
Username string `json:"username"`
QuestionID string `json:"question_id"`
SelectedOption string `json:"selected_option"`
}
if err := ctx.Bind(&request); err != nil {
return nil, http.ErrorInvalidParam{Params: []string{"body"}}
}
value, err := ctx.KVStore.Get(ctx, request.QuestionID)
if err != nil {
return nil, fmt.Errorf("question not found")
}
var question Question
if err := json.Unmarshal([]byte(value), &question); err != nil {
return nil, fmt.Errorf("failed to parse question data")
}
score := 0
if request.SelectedOption == question.CorrectAnswer {
score = 1 // Increment score for correct answer
}
currentScoreKey := fmt.Sprintf("score:%s", request.Username)
scoreData := Score{Username: request.Username, Score: score}
scoreJSON, _ := json.Marshal(scoreData)
if err := ctx.KVStore.Set(ctx, currentScoreKey, string(scoreJSON)); err != nil {
return nil, err
}
return map[string]string{"message": "Answer submitted!"}, nil
}
func GetLeaderboard(ctx *gofr.Context) (interface{}, error) {
scores := []Score{}
for _, key := range ctx.KVStore.GetKeys("score:*") { // Hypothetical function to get all keys matching pattern
value, err := ctx.KVStore.Get(ctx, key)
if err == nil {
var score Score
json.Unmarshal([]byte(value), &score)
scores = append(scores, score)
}
}
sort.Slice(scores, func(i, j int) bool { return scores[i].Score > scores[j].Score })
return scores[:10], nil // Return top 10 scores
}
Testing the System
You can use Postman or curl to test each endpoint. For example, to create a new question:
curl -X POST http://localhost:9000/question \
-H "Content-Type: application/json" \
-d '{"question":"What is the capital of France?","options":["Berlin","Madrid","Paris","Rome"],"correct_answer":"Paris"}'
Conclusion
This project demonstrates the power of using NATS Key-Value Store with the Gofr framework. The combination provides an efficient way to build scalable applications that require fast data retrieval and storage. The Trivia Quiz Application offers a fun way for users to engage with trivia questions while keeping track of their scores.
Feel free to explore this project further!
If you found this article helpful or interesting, consider starring the Gofr repository on GitHub!
To know more about GoFr check out their official documentationand
Github Repo