Recipe WebApp

A full-stack recipe management application with REST API, JWT authentication, and OCR scanning.

In active development — backend services and REST API complete; frontend in progress.

Overview

A web application for recording, organizing, and sharing family recipes — built around a Go REST API that handles all business logic server-side.

The core problem: family recipes live on handwritten cards, in text messages, or scattered across devices. This app centralizes them behind a proper API with user accounts, so recipes are accessible from anywhere and shareable between family members.

The backend is the primary engineering focus. It exposes a RESTful JSON API built with Go and the Gin framework, backed by PostgreSQL. The frontend is a React application that consumes the API — currently in development.

Concepts

UI designs created in Figma, showing desktop and mobile responsive layouts.

Architecture

The system follows a standard three-tier architecture:

React Frontend (SPA)
    |
    v
Gin REST API  (Go)  —  stateless, JWT-authenticated
    |          \
    v           v
PostgreSQL    OCR.space API  (third-party, proxied)
                

Client → API → Database. The React frontend makes JSON requests to the Gin API. The API owns all business logic: input validation, authentication, authorization, and database access. PostgreSQL stores all persistent data.

Backend proxy pattern for external APIs. The OCR feature (image-to-text scanning) calls the OCR.space third-party API, but the frontend never talks to it directly. Instead, the frontend uploads an image to the Go API, which proxies the call to OCR.space server-side. This keeps API keys off the client, normalizes the response format, and lets the backend enforce authentication before any external call is made.

Service architecture. The API binary runs as a system service via kardianos/service, supporting graceful startup/shutdown with configurable timeouts. This means the same binary works as a foreground process during development and as a managed service in production.

API Design

The API is organized into resource groups, each with its own route file, handler, and store interface:

Group Endpoints Purpose
/recipes GET / — list all
GET /:id — get one
POST / — create
PATCH /:id — partial update
DELETE /:id — delete
Full CRUD for recipes. PATCH accepts partial payloads — only the fields you send are updated.
/sessions POST / — login (create session)
GET / — validate session *
DELETE / — logout *
Session management. POST takes username + password, returns a signed JWT cookie. * = requires valid JWT.
/image-to-text POST / — parse image * Upload an image, receive extracted text. Proxies to OCR.space API server-side. * = requires valid JWT. Conditionally enabled via config.
/status GET / — health check Returns service status. No auth required — used by the frontend before login.

Layered handler pattern. Each resource group follows the same structure: a route file wires endpoints to a handler struct, which depends on a store interface. The store interface abstracts the database layer, making handlers fully unit-testable with mock implementations — and every handler has tests.

Authentication

Authentication uses a custom JWT implementation with HMAC-SHA256 signing:

  1. Login: Client sends POST /sessions with username and password. The API verifies the password hash, builds a JWT containing the user ID, issuer, issued-at, and expiration timestamps, signs it with HS256, and sets it as an HTTP cookie.
  2. Validation: Protected routes use a Gin middleware (AuthenticateJWT) that extracts the JWT from the cookie, re-hashes header + payload to verify the signature hasn't been tampered with, and checks expiration. If valid, the user ID is injected into the Gin context for downstream handlers.
  3. Logout: DELETE /sessions clears the session cookie.

The JWT secret, session duration, cookie name, and secure flag are all configurable via environment variables. Session duration defaults to 15 minutes.

What it protects: Session validation/deletion and the image-to-text endpoint sit behind the JWT middleware. Recipe CRUD endpoints have auth enforcement planned (with TODO annotations in the route definitions for authorization checks).

Data Model

PostgreSQL with versioned migrations managed by golang-migrate. The API creates its own schema (recipeapi) on startup.

Table Columns Notes
users id (bigserial PK)
username (varchar, unique)
passwordhash (varchar)
created_at (timestamptz)
Passwords stored as hashes, never plaintext.
recipes id (bigserial PK)
name (varchar)
description (text)
created_at (timestamptz)
creator_id (integer, FK → users)
Foreign key to users added via migration 005. Enforces NOT NULL — every recipe belongs to a user.

Migration history: 5 migrations so far — initial recipe table creation, default timestamp fix, description NOT NULL constraint, users table creation, and the creator_id foreign key linking recipes to users.

Go structs in the models package define the data shapes used throughout the API, with JSON tags for serialization. Request bodies use separate structs (e.g., PatchRecipeRequestBody with pointer fields for partial updates).

Feature Highlights

OCR Image-to-Text Scanning

Users can photograph a handwritten recipe card and have the text extracted automatically. The implementation:

  • Frontend uploads the image as multipart form data to POST /image-to-text.
  • The handler saves the upload to a temp file, auto-resizes it if it exceeds the OCR API's file size limit, then passes it to the OCR store.
  • The store calls OCR.space (Engine 3) via the go_ocr_space library and returns the extracted text.
  • The backend returns the text as JSON. The frontend can then pre-fill a recipe creation form with it.

This is a backend proxy pattern: the API key stays server-side, the frontend only needs to upload an image and receive text. The endpoint is JWT-protected so only authenticated users can use it, and it's conditionally registered — if no OCR API key is configured, the route doesn't exist.

AI Recipe Generation (Planned)

The software requirements specification includes AI-powered recipe generation via prompt, allowing users to describe a dish and receive a generated recipe. This feature is planned but not yet implemented in the current codebase.

Structured Logging & Error Monitoring

The API uses logrus for structured logging with configurable log levels and a readable formatter for development. Production error monitoring is integrated via Sentry — when a Sentry DSN is configured, error-level logs are forwarded automatically with stack traces attached.

Deployment

Containerized with Docker. The API uses a multi-stage Dockerfile: a Go builder stage compiles the binary, then copies it (along with migration files) into a minimal Alpine image. The result is a small, self-contained container.

Docker Compose orchestrates the full stack locally and in production: the API service and a PostgreSQL database container with health checks. The DB container waits until migrations have run and data is queryable before the API starts accepting traffic.

CI/CD: A GitHub Actions workflow triggers on release creation, builds the Go binary for Windows, and attaches it to the GitHub release as a downloadable artifact.

Configuration is entirely environment-driven. Database credentials, JWT secret, session settings, OCR API key, Gin mode, Sentry DSN, and log level are all set via environment variables (with sensible defaults where appropriate). A .env file is supported for local development via godotenv.

Tech Stack

Language
Go
Web Framework
Gin
Database
PostgreSQL
Auth
Custom JWT (HS256), cookie-based
Migrations
golang-migrate
OCR
OCR.space API via go_ocr_space
Logging
Logrus + Sentry
Service Management
kardianos/service
Frontend
React (in progress)
Containerization
Docker + Docker Compose
CI/CD
GitHub Actions
Testing
Go testing with mock store interfaces

Documents

Software Requirements Specification

Project requirements, interface specs, and design constraints

GitHub

View the source code

github.com/SuperNinjaFat/Recipe-API