SLC-S23W6 - Backend & golang- Candidate Evaluation Platform

Welcome to the final week of our Tech & Dev Club learning series! Over the past five weeks, we've covered project planning, containerization with Docker, frontend development with React, and database management. Now, it's time to tie everything together with backend development using Golang.
What You'll Learn This Week
- Golang as a backend language
- RESTful API design principles
- API documentation with Swagger
- Authentication and middleware
- Controller-Service-Repository pattern
- Real examples from the Candidly project
Why Golang for Backend Development?
Golang offers several benefits for backend development:
- Performance: Compiled to machine code, making it very fast
- Concurrency: Goroutines make concurrent operations simple
- Simplicity: Easy to learn with clean syntax
- Strong Standard Library: Includes HTTP server components
- Growing Ecosystem: Many third-party packages available
Project Architecture
Our backend follows a layered architecture:

Backend Architecture Diagram
- Router Layer: Handles HTTP requests
- Controller Layer: Processes input and returns responses
- Service Layer: Contains business logic
- Repository Layer: Interacts with the database
- Model Layer: Defines data structures
Setting Up the Server
Here's how we initialize our server in Golang:
// server/server.go
package server
import (
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/cors"
"github.com/gofiber/fiber/v2/middleware/logger"
"log"
)
func Init() {
app := fiber.New()
// Middleware
app.Use(logger.New())
app.Use(cors.New())
// Setup routes
SetupRoutes(app)
// Start server
log.Fatal(app.Listen(":8080"))
}
API Routing with Fiber

API Request Flow Diagram
We use the Fiber framework to define our API routes:
// server/router.go
package server
import (
"github.com/dev/test/controllers"
"github.com/dev/test/middleware"
"github.com/gofiber/fiber/v2"
)
func SetupRoutes(app *fiber.App) {
// API group
api := app.Group("/api")
// Public routes
api.Post("/login", controllers.Login)
// Auth middleware for protected routes
auth := api.Group("/")
auth.Use(middleware.JWTAuth())
// Questions routes
auth.Get("/questions", controllers.GetQuestions)
auth.Post("/questions/edit", controllers.CreateQuestion)
auth.Get("/questions/edit/:id", controllers.GetQuestion)
auth.Post("/questions/edit/:id", controllers.UpdateQuestion)
// Tests routes
auth.Post("/my-tests", controllers.CreateTest)
auth.Get("/my-tests/:id", controllers.GetTest)
auth.Post("/my-tests/:test_id", controllers.UpdateTest)
// And more routes...
}
JWT Authentication

Authentication Flow Diagram
Security is crucial for our application. We implement JWT-based authentication:
// middleware/jwt_auth.go
package middleware
import (
"github.com/gofiber/fiber/v2"
"github.com/golang-jwt/jwt/v4"
"os"
"strings"
)
func JWTAuth() fiber.Handler {
return func(c *fiber.Ctx) error {
// Get authorization header
authHeader := c.Get("Authorization")
// Check if auth header exists
if authHeader == "" {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
"error": "Unauthorized",
})
}
// Extract the token
tokenString := strings.Replace(authHeader, "Bearer ", "", 1)
// Parse and validate the token
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return []byte(os.Getenv("JWT_SECRET")), nil
})
if err != nil || !token.Valid {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
"error": "Invalid or expired token",
})
}
// Set the user ID in context for controllers to access
claims := token.Claims.(jwt.MapClaims)
c.Locals("userID", claims["id"])
return c.Next()
}
}
Controller Layer
Controllers handle HTTP requests and responses:
// controllers/question.go
package controllers
import (
"github.com/dev/test/models"
"github.com/dev/test/services"
"github.com/gofiber/fiber/v2"
"strconv"
)
// GetQuestions returns all questions
func GetQuestions(c *fiber.Ctx) error {
// Get query parameters
questionType := c.Query("type")
difficulty := c.Query("difficulty")
// Get user ID from context
userID := c.Locals("userID").(float64)
// Get questions using service
questions, err := services.GetQuestionsByFilter(uint(userID), questionType, difficulty)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "Could not retrieve questions",
})
}
return c.JSON(questions)
}
// CreateQuestion creates a new question
func CreateQuestion(c *fiber.Ctx) error {
var input models.CreateQuestionInput
// Parse request body
if err := c.BodyParser(&input); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Invalid input",
})
}
// Validate input
if err := validate.Struct(input); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Validation failed",
})
}
// Get user ID from context
userID := c.Locals("userID").(float64)
// Create question using service
question, err := services.CreateQuestion(input, uint(userID))
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "Could not create question",
})
}
return c.JSON(question)
}
// GetQuestion returns a specific question
func GetQuestion(c *fiber.Ctx) error {
id, err := strconv.ParseUint(c.Params("id"), 10, 32)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Invalid ID format",
})
}
question, err := services.GetQuestionByID(uint(id))
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
"error": "Question not found",
})
}
return c.JSON(question)
}
// UpdateQuestion updates a specific question
func UpdateQuestion(c *fiber.Ctx) error {
id, err := strconv.ParseUint(c.Params("id"), 10, 32)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Invalid ID format",
})
}
var input models.CreateQuestionInput
if err := c.BodyParser(&input); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Invalid input",
})
}
// Validate input
if err := validate.Struct(input); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Validation failed",
})
}
question, err := services.UpdateQuestion(uint(id), input)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "Could not update question",
})
}
return c.JSON(question)
}
Service Layer
Services contain business logic:
// services/question.go
package services
import (
"github.com/dev/test/models"
"github.com/dev/test/repositories"
)
// GetQuestionsByFilter retrieves questions by type and difficulty
func GetQuestionsByFilter(userID uint, questionType, difficulty string) ([]models.Question, error) {
return repositories.GetQuestionsByFilter(userID, questionType, difficulty)
}
// CreateQuestion creates a new question
func CreateQuestion(input models.CreateQuestionInput, userID uint) (models.Question, error) {
// Create the question entity
question := models.Question{
Name: input.Name,
QuestionText: input.QuestionText,
Difficulty: input.Difficulty,
Type: input.Type,
ExpectedTime: input.ExpectedTime,
MaxPoints: input.MaxPoints,
FileReadMe: input.FileReadMe,
UserID: userID,
}
// Handle skill association
if input.SkillID > 0 {
skill, err := repositories.GetSkillByID(input.SkillID)
if err == nil {
question.SkillID = skill.ID
}
} else if input.SkillName != "" {
// Try to find existing skill or create new one
skill, err := repositories.GetSkillByName(input.SkillName)
if err != nil {
// Create new skill
newSkill, err := repositories.CreateSkill(models.Skill{Name: input.SkillName})
if err == nil {
question.SkillID = newSkill.ID
}
} else {
question.SkillID = skill.ID
}
}
// Save the question
savedQuestion, err := repositories.CreateQuestion(question)
if err != nil {
return models.Question{}, err
}
// Save choices if present
if len(input.Choices) > 0 {
for _, c := range input.Choices {
choice := models.Choices{
ChoiceText: c.ChoiceText,
IsAnswer: c.IsAnswer,
QuestionID: savedQuestion.ID,
}
_, err := repositories.CreateChoice(choice)
if err != nil {
return savedQuestion, err
}
}
}
// Return the saved question with all relations loaded
return repositories.GetQuestionByID(savedQuestion.ID)
}
// Additional service methods...
Repository Layer
Repositories handle database operations:
// repositories/question.go
package repositories
import (
"github.com/dev/test/database"
"github.com/dev/test/models"
)
// GetQuestionsByFilter retrieves questions by type and difficulty
func GetQuestionsByFilter(userID uint, questionType, difficulty string) ([]models.Question, error) {
var questions []models.Question
query := database.DB
// Apply filters if provided
if questionType != "" {
query = query.Where("type = ?", questionType)
}
if difficulty != "" {
query = query.Where("difficulty = ?", difficulty)
}
// Only show questions created by this user
query = query.Where("user_id = ?", userID)
// Execute query with preloaded relations
result := query.Preload("Choices").Preload("Skill").Find(&questions)
return questions, result.Error
}
// GetQuestionByID retrieves a question by ID
func GetQuestionByID(id uint) (models.Question, error) {
var question models.Question
result := database.DB.
Preload("Choices").
Preload("Skill").
First(&question, id)
return question, result.Error
}
// CreateQuestion creates a new question
func CreateQuestion(question models.Question) (models.Question, error) {
result := database.DB.Create(&question)
return question, result.Error
}
// Additional repository methods...
API Documentation with Swagger
One of the most important aspects of API development is documentation. We use Swagger to document our API endpoints:

How Swagger Work
Setting Up Swagger
We use swaggo/swag to generate Swagger documentation from code annotations:
// main.go
package main
import (
"github.com/dev/test/docs"
"github.com/dev/test/server"
"os"
)
// @title test
// @version 1.0
// @description this is an application web of interview assessment tests for interviewing out of the box.
// @securityDefinitions.apikey ApiKeyAuth
// @in header
// @name Authorization
// @BasePath /api
func main() {
// Set JWT secret key
os.Setenv("key", "123456781234567812345678")
// Initialize swagger info
docs.SwaggerInfo.Title = "Candidly API"
docs.SwaggerInfo.Description = "API for interview assessment tests"
docs.SwaggerInfo.Version = "1.0"
docs.SwaggerInfo.BasePath = "/api"
// Start server
server.Init()
}
Annotating Controllers for Swagger
We use special comments to annotate our controllers for Swagger documentation:
// controllers/question.go
// GetQuestions godoc
// @Summary find a question
// @Description find a question by type or difficulty
// @Tags question
// @Accept json
// @Produce json
// @Param type query string false "question search by type"
// @Param difficulty query string false "question search by difficulty"
// @Success 200 {array} models.Question
// @Security Authorization
// @Router /questions [get]
func GetQuestions(c *fiber.Ctx) error {
// Implementation...
}
// CreateQuestion godoc
// @Summary add new question
// @Description create new question by json
// @Tags question
// @Accept json
// @Produce json
// @Param question body models.CreateQuestionInput true "Add question"
// @Success 200 {object} models.Question
// @Security Authorization
// @Router /questions/edit [post]
func CreateQuestion(c *fiber.Ctx) error {
// Implementation...
}
Generated Swagger Documentation
The swagger annotations generate comprehensive API documentation:
{
"/questions": {
"get": {
"security": [
{
"Authorization": []
}
],
"description": "find a question by type or difficulty",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"question"
],
"summary": "find a question",
"parameters": [
{
"type": "string",
"description": "question search by type",
"name": "type",
"in": "query"
},
{
"type": "string",
"description": "question search by difficulty",
"name": "difficulty",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/models.Question"
}
}
}
}
}
}
}
Integration Testing

Test Evaluation Process Diagram
Testing is crucial for ensuring reliable API endpoints. We use Go's testing package:
// controllers/question_test.go
package controllers
import (
"encoding/json"
"github.com/dev/test/models"
"github.com/gofiber/fiber/v2"
"github.com/stretchr/testify/assert"
"io/ioutil"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func TestGetQuestions(t *testing.T) {
// Setup
app := fiber.New()
app.Get("/api/questions", func(c *fiber.Ctx) error {
c.Locals("userID", float64(1)) // Mock authenticated user
return GetQuestions(c)
})
// Execute request
req := httptest.NewRequest("GET", "/api/questions?type=MCQ&difficulty=easy", nil)
resp, _ := app.Test(req)
// Assert status code
assert.Equal(t, http.StatusOK, resp.StatusCode)
// Parse response body
body, _ := ioutil.ReadAll(resp.Body)
var questions []models.Question
json.Unmarshal(body, &questions)
// Assert response
assert.NotEmpty(t, questions)
assert.Equal(t, "MCQ", questions[0].Type)
assert.Equal(t, "easy", questions[0].Difficulty)
}
func TestCreateQuestion(t *testing.T) {
// Setup
app := fiber.New()
app.Post("/api/questions/edit", func(c *fiber.Ctx) error {
c.Locals("userID", float64(1)) // Mock authenticated user
return CreateQuestion(c)
})
// Prepare request
questionJSON := `{
"name": "Test Question",
"question_text": "What is the capital of France?",
"type": "MCQ",
"difficulty": "easy",
"expected_time": 60,
"max_points": 10,
"choices": [
{
"choice_text": "Paris",
"is_answer": true
},
{
"choice_text": "London",
"is_answer": false
}
]
}`
// Execute request
req := httptest.NewRequest("POST", "/api/questions/edit", strings.NewReader(questionJSON))
req.Header.Set("Content-Type", "application/json")
resp, _ := app.Test(req)
// Assert status code
assert.Equal(t, http.StatusOK, resp.StatusCode)
// Parse response body
body, _ := ioutil.ReadAll(resp.Body)
var question models.Question
json.Unmarshal(body, &question)
// Assert response
assert.Equal(t, "Test Question", question.Name)
assert.Equal(t, "MCQ", question.Type)
assert.Equal(t, 2, len(question.Choices))
}
The Complete System
Here's how all the components work together:
Complete System Flow
- Frontend (React) sends HTTP requests to the backend API
- Backend Router directs the request to the appropriate controller
- JWT Authentication Middleware verifies user credentials
- Controller processes the request and calls the service layer
- Service Layer implements business logic
- Repository Layer interacts with the database
- Database (PostgreSQL) stores and retrieves data
- Response flows back through the layers to the frontend
What I Learned
Developing the backend for Candidly taught me several valuable lessons:
- API Design: RESTful API design principles are crucial for a clean, maintainable API
- Authentication: JWT provides a secure, stateless authentication mechanism
- Clean Architecture: Separating concerns into controllers, services, and repositories improves maintainability
- Documentation: Swagger makes API documentation a breeze
- Testing: Comprehensive testing ensures reliability
Conclusion
Over these six weeks, we've built a complete interview assessment platform from planning to implementation. The backend, built with Golang, ties everything together, providing a robust API for the frontend to consume.
The combination of Golang's performance, the Fiber framework's simplicity, and Swagger's documentation capabilities make for a powerful backend stack that can handle complex business logic while remaining maintainable and scalable.
This concludes our learning series on building a complete web application. I hope you've gained valuable insights into modern web development practices. Feel free to explore the repositories and continue learning!
GitHub Repositories
- Frontend: TalentLens
- Backend: Candidly
Project Resources
- Complete System Overview: Google Drive Link
- Project Presentation Video:

Authentication interface

Question type selection interface

Interface for adding questions to a test

Interface for adding an MCQ-type question

Interface for inviting a candidate to a test

Candidate welcome interface
What can you share in the club?
Our club is all about technology and development including:
- Web & Mobile Development
- AI & Machine Learning
- DevOps & Cloud Technologies
- Blockchain & Decentralized Applications
- Open-source Contributions
- Graphic Design & UI/UX
Any posts related to technology, reviews, information, tips, and practical experience must include original pictures, real-life reviews of the product, the changes it has brought to you, and a demonstration of practical experience
The club is open to everyone. Even if you're not interested in development, you can still share ideas for projects, and maybe someone will take the initiative to work on them and collaborate with you. Don't worry if you don't have much IT knowledge, just share your great ideas with us and provide feedback on the development projects. For example, if I create an extension, you can give your feedback as a user, test the specific project, and that will make you an important part of our club. We encourage people to talk and share posts and ideas related to Steemit.
For more information about the #techclub you can go through A New Era of Learning on Steemit: Join the Technology and Development Club. It has all the details about posting in the "#techclub" and if you have any doubts or needs clarification you can ask.
Our Club Rules
To ensure a fair and valuable experience, we have a few rules:
- No AI generated content. We want real human creativity and effort.
- Respect each other. This is a learning and collaborative space.
- No simple open source projects. If you submit an open source project, ensure you add significant modifications or improvements and share the source of the open source.
- Project code must be hosted in a Git repository (GitHub/GitLab preferred). If privacy is a concern, provide limited access to our mentors.
- Any post in our club that is published with the main tag "#techclub" please mention the mentors @kafio @mohammadfaisal @alejos7ven
- Use the tag #techclub, #techclub-s23w6, "#country", other specific tags relevant to your post.
- In this week's "#techclub" you can participate from Monday, March 24, 2025, 00:00 UTC to Sunday, March 30, 2025, 23:59 UTC.
- Post the link to your entry in the comments section of this contest post. (Must)
- Invite at least 3 friends to participate.
- Try to leave valuable feedback on other people's entries.
- Share your post on Twitter and drop the link as a comment on your post.
Each post will be reviewed according to the working schedule as a major review which will have all the information about the post such as AI, Plagiarism, Creativity, Originality and suggestions for the improvements.
Other team members will also try to provide their suggestions just like a simple user but the major review will be one which will have all the details.
Rewards System
Sc01 and Sc02 will be visiting the posts of the users and participating teaching teams and learning clubs and upvoting outstanding content. Upvote is not guaranteed for all articles. Kindly take note.
Each week we will select Top 4 posts which has outperformed from others while maintaining the quality of their entry, interaction with other users, unique ideas, and creativity. These top 4 posts will receive additional winning rewards from SC01/SC02.
Note: If we find any valuable and outstanding comment than the post then we can select that comment as well instead of the post.
Technology and Development Club Team


cc:@steemcurator01
Upvoted! Thank you for supporting witness @jswit.