Files
microservice-importer/README.md
T

12 KiB

Microservice Importer

Standalone-tjänst för snabbimport av recept från webben. Extraherar receptdata (namn, beskrivning, ingredienser, instruktioner) från URL:er och konverterar till standardiserad Markdown-format.

Kopplat till: recipe-app — men kan användas helt oberoende.


Features

Quick-Import via URL

  • Skrapa från ICA.se — JSON-LD structured data + HTML-parsing
  • Generisk parser — Fallback för andra webbplatser
  • Automatisk extraction:
    • Receptnamn
    • Beskrivning (från meta-taggar eller JSON-LD)
    • Ingredienser med kvantitet, enhet och noter
    • Instruktioner/tillvägagångssätt
    • Källlänk (läggs till i footer)

Stödd format

  • Bråkmängder: 1 1/2 dl, 1/2 tsk
  • Enheter: g, kg, ml, dl, msk, tsk, st, port, efter smak, förp, klyfta, m.fl.
  • Parentetiska noter: 2 dl grädde (vispgrädde) → note sparas separat
  • Markdown-output: Strukturerat receptformat för vidare bearbetning

Parse-Markdown endpoint

Tolka Markdown-format recepter utan databaskomplikationer. Användbar för API-integration utan lokal DB.


Arkitektur

Backend (NestJS 10.3, TypeScript 5.4.5)

Port: 3001

src/
├── app.module.ts                      # Root module (Quick-import + Recipes)
├── main.ts                            # Startpunkt
├── common/
│   ├── filters/
│   │   └── global-exception.filter.ts # Centraliserad felhantering (svenska meddelanden)
│   └── utils/
│       └── normalize-name.ts          # Namnormalisering (åäö-handling)
├── quick-import/                      # URL-scraping & parsing
│   ├── quick-import.controller.ts     # POST /api/quick-import
│   ├── quick-import.service.ts        # Scraping-logik, parser-selection
│   ├── quick-import.module.ts
│   └── parsers/
│       ├── base.parser.ts             # Abstrakt bas-klass
│       ├── ica.parser.ts              # ICA.se-specifik (JSON-LD prioritet)
│       └── generic.parser.ts          # Fallback för alla webbplatser
└── recipes/                           # Markdown-tolkning (SOM DB!)
    ├── recipes.controller.ts          # POST /api/recipes/parse-markdown
    ├── recipes.service.ts             # Enkel markdown-parsing
    ├── recipes.module.ts
    └── dto/
        └── parse-markdown.dto.ts      # Validering

Viktigt: Backend har INGEN databaskonfiguration (@prisma/client inte installerat).

Frontend (Next.js 16.2, React 19.2, TypeScript 5.4.5)

Port: 3000

app/
├── layout.tsx                         # Root layout
├── page.tsx                           # Home page
├── Navigation.tsx                     # Minimal nav (Home + Import)
├── import/page.tsx                    # PRIMARY FEATURE — Import UI
│   └── Inmatningsfält för URL/filsökväg
│   └── Visa resultat i realtid
├── api/
│   └── parse-markdown-proxy/route.ts  # API proxy till backend
└── lib/
    ├── api.ts                         # Centraliserad API-access (fetchJson)
    └── error-handler.ts               # parseErrorResponse (svenska meddelanden)

Quick-Start

Prerequisites

  • Node.js 22.x
  • Docker & Docker Compose (valfritt)

Local Development

# 1. Installera backend
cd backend
npm install
npm run start:dev
# Backend kör på http://localhost:3001

# 2. (I ny terminal) Installera frontend
cd frontend
npm install
npm run dev
# Frontend kör på http://localhost:3000

Öppna http://localhost:3000 → Gå till /import → Klistra in URL

Docker

# Bygg och starta
docker compose up -d

# Frontend: http://localhost:3000
# Backend: http://localhost:3001

Stoppa:

docker compose down

API-dokumentation

POST /api/quick-import

Syfte: Skrapa webbsida och returnera Markdown-recept

Request:

{
  "input": "https://ica.se/recept/kottfarssas-1234"
}

Response (Success 200):

{
  "markdown": "# Köttfärssås\n\nEn klassisk...\n\n## Ingredienser\n- 500 g köttfärs\n...",
  "source": "ica"
}

Response (Error 400/503):

{
  "statusCode": 400,
  "message": "Kunde inte hämta recept: HTTP 404. Kontrollera att länken är korrekt och försök igen.",
  "timestamp": "2026-04-12T10:30:00.000Z"
}

Error-scenarier:

  • 400 — Tomt input, inte en URL, inte en filsökväg
  • 400 — HTML-parsing misslyckades (receptnamn/ingredienser inte hittade)
  • 503 — Network-fel (t.ex. webbsidan nåbar, men HTTP 500 från server)

POST /api/recipes/parse-markdown

Syfte: Tolka Markdown-receptformat utan database

Request:

{
  "markdown": "# Receptnamn\n\n## Ingredienser\n- 500 g köttfärs\n\n## Tillvägagångssätt\nStek löken..."
}

Response (Success 200):

{
  "name": "Receptnamn",
  "description": "",
  "instructions": "Stek löken...",
  "ingredients": [
    {
      "rawName": "köttfärs",
      "quantity": 500,
      "unit": "g",
      "note": null
    }
  ]
}

Parser-arkitektur

Bas-parser (base.parser.ts)

Abstrakt klass som alla parsers ärver från. Innehåller gemensam parsing-logik:

abstract class RecipeParser {
  abstract canHandle(url: string): boolean;
  abstract parse(html: string): ParsedRecipe;
  
  protected parseIngredientLine(line: string): ParsedIngredient | null {
    // Shared logic för ingrediensparsning
  }
}

parseIngredientLine-funktionen hanterar:

  • 500 g köttfärs{quantity: 500, unit: "g", name: "köttfärs"}
  • 1 1/2 dl grädde (vispgrädde){quantity: 1.5, unit: "dl", name: "grädde", note: "vispgrädde"}
  • 3 ägg{quantity: 3, unit: "st", name: "ägg"}
  • salt{quantity: 0, unit: "", name: "salt"}

Kända enheter:

Vikt: g, kg, hg, mg
Volym: ml, dl, l, tl
Portioner: tsk, msk, krm
Övriga: st, port, burk, förp, paket, pris, portion, matsked, tesked, efter smak, klyfta

ICA Parser (ica.parser.ts)

Optimerad för ICA.se receptsidor.

Strategi:

  1. Försök extrahera JSON-LD structured data (prioritet)
  2. Fallback: HTML-regex parsing

JSON-LD mål:

  • Receptnamn från @type === "Recipe".name
  • Beskrivning från .description
  • Ingredienser från .recipeIngredient[]
  • Instruktioner från .recipeInstructions[]

HTML-fallback:

  • Titel: <h1> eller <meta property="og:title">
  • Beskrivning: <meta name="description">
  • Ingredienser: <li class="...ingredient..."> regex
  • Instruktioner: <div class="...instruction..."> regex

Generic Parser (generic.parser.ts)

Fallback-parser för alla okända webbplatser.

Strategi:

  1. Försök JSON-LD structured data (alla webbplatser kan ha detta)
  2. Fallback: Permissiv HTML-parsing

HTML-parsing försöker flera selectors:

  • Ingredienser: <li>, <div class="ingredient">, <p class="ingredient">
  • Instruktioner: <div class="instruction">, <ol> listor
  • Titel: <h1>, <meta property="og:title">, <title>

Markdown-format och parsing

Input-format

# Receptnamn

Valfri beskrivning av receptet (1+ stycken).

## Ingredienser
- 500 g köttfärs
- 1 st lök
- 2.5 msk tomatpuré
- 1 dl grädde (vispgrädde)
- salt

## Tillvägagångssätt
Steg 1: Stek löken i smör.
Steg 2: Tillsätt köttfärsen och stek tills den är genomstekt.

Parsningsregler

Element Tolkning
# Rubrik Receptnamn (första H1)
Text mellan H1 och ## Ingredienser Beskrivning (flera rader OK, valfritt)
## Ingredienser Ingredient-markerare (case-insensitive)
- ANTAL ENHET NAMN Ingrediens med alla delar
- ANTAL NAMN Ingrediens utan enhet (unit → "st")
- NAMN Ingrediens utan kvantitet (quantity → 0)
(text i parentes) Ingrediensnot (sparas separat)
## Tillvägagångssätt / ## Instruktioner / ## Tillagning Instruktions-markerare
Text under instruktioner Tillagningssteg (flera rader OK)

Exempel:

Input:   "- 1,5 dl grädde (vispgrädde)"
Output:  {quantity: 1.5, unit: "dl", rawName: "grädde", note: "vispgrädde"}

Input:   "- 3 ägg"
Output:  {quantity: 3, unit: "st", rawName: "ägg", note: null}

Input:   "- salt"
Output:  {quantity: 0, unit: "", rawName: "salt", note: null}

Deployment

cd microservice-importer
docker compose up -d

Services:

  • importer-api — NestJS backend (port 3001)
  • importer-frontend — Next.js frontend (port 3000)

Stoppa:

docker compose down

Loggar:

docker compose logs -f importer-api
docker compose logs -f importer-frontend

Manuell Docker build

# Backend
docker build -f backend/Dockerfile -t importer-api:local .
docker run -p 3001:3001 importer-api:local

# Frontend (ny terminal)
docker build -f frontend/Dockerfile -t importer-frontend:local .
docker run -p 3000:3000 importer-frontend:local

Environment variables

Konfigureras i docker compose eller .env:

Backend:

PORT=3001
NODE_ENV=production

Frontend:

NEXT_PUBLIC_API_URL_INTERNAL=http://importer-api:3001

Integration med Recipe App

Recipe App kan anropa denna microservice som extern API:

const IMPORTER_URL = process.env.MICROSERVICE_IMPORTER_URL || 'http://localhost:3001';

const response = await fetch(`${IMPORTER_URL}/api/quick-import`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ input: url })
});

const { markdown, source } = await response.json();

// Sedan kan recipe-app:s parse-markdown-endpoint
// använda Markdown för DB-matchning mot produkter

Fil-struktur & Koddelning

Följande filer är identiska mellan recipe-app och microservice-importer:

  • backend/src/quick-import/parsers/base.parser.ts
  • backend/src/quick-import/parsers/ica.parser.ts
  • backend/src/quick-import/parsers/generic.parser.ts
  • backend/src/common/filters/global-exception.filter.ts
  • backend/src/common/utils/normalize-name.ts

Framtida improvement: Dessa kan förpackas som separat npm-paket för bättre koddelning och versionering.


Tekniska detaljer

Backend Stack

  • NestJS 10.3 — REST API & modular architecture
  • TypeScript 5.4.5 — Type safety
  • Node.js 22.x — Runtime
  • class-validator — DTO validation (svenska felmeddelanden)
  • Ingen databas — Stateless service

Frontend Stack

  • Next.js 16.2 — React framework (App Router)
  • React 19.2 — UI components
  • TypeScript 5.4.5
  • Inline CSS — Minimal styling, no framework dependencies

Error Handling

  • Centraliserad GlobalExceptionFilter (svenska meddelanden)
  • Konsistent JSON-responsformat
  • HTTP status codes: 200, 400, 503

Framtida utbyggnader

  • PDF-import (stubben redan på plats)
  • Stöd för fler webbplatser (mat.se, kokaihop.se, etc.)
  • Caching av parsed recept
  • Rate limiting för scraping
  • WebSocket support för real-time parsing
  • GraphQL endpoint

Licens

Samma som Recipe App — se main repo


Support

Relaterade projekt:

  • Recipe Apprecipe-app (Full platform med databas + quick-import integrerad)
  • Git Repo — Gitea på 192.168.50.2:2222/nilsjohan/microservice-importer