MB
~ / projects / educado

Educado_

educado.es

School search platform for expat families in Spain. Helps parents find, compare, and choose the right school based on language, curriculum, location, and budget.

Laravel 12 Golang Vue.js 3 Inertia.js v2 PostgreSQL Elasticsearch Filament v3 OpenAI
48 controllers
26+ services
80+ routes
115 tests
70+ migrations

// architecture

Modular monolith + Go microservice. Laravel handles the web layer and API, while the Go service runs resource-intensive data enrichment. Both share a single PostgreSQL database and deploy independently.

$ cat architecture.txt
Browser
Caddy (TLS)
┌─────┴─────┐
Laravel 12 Go Service
├ Public site ├ Google Places API
├ Dashboard SPA ├ OpenAI API
├ Admin (Filament) ├ SEO generation
└ REST API └ Translation
└─────┬─────┘
PostgreSQL · Redis · Elasticsearch
DDD Approach

Services, DTOs, enums, value objects, contract interfaces

Event-Driven

13 domain events, 14 listeners — async side-effects via queues

Pipeline Pattern

4-stage enrichment pipeline in Go with idempotent jobs

Polymorphic SEO

HasSeo trait — attach meta to any model with Schema.org JSON-LD

Islands Architecture

Public: Blade + Alpine.js, Dashboard: full Inertia SPA

Clean Go Architecture

cmd → internal → pkg, custom error types, structured logging

// data enrichment pipeline

Standalone Go microservice that enriches school data through a 4-stage pipeline. Each job is idempotent with precondition checks and safe retries.

$ ./enrichment-service --pipeline full
Stage 1
Google Places
Search school → extract ratings, reviews, photos, coordinates
Stage 2
OpenAI Analysis
Parse school website → generate structured descriptions, programs, languages
Stage 3
SEO Generation
Generate meta tags and Schema.org markup for each locale
Stage 4
Translation
Auto-translate ES ↔ EN with caching layer
// internal/pipeline/enrichment.go
type Pipeline struct {
stages []Stage
db *gorm.DB
}
type Stage interface {
Name() string
Run(ctx context.Context, school *School) error
ShouldRun(school *School) bool
}

// key features

01. School Catalog

  • 4-level filtering (basic, advanced, expat-specific, sorting)
  • SEO URLs with 11 segment types (/colegios/madrid/privados)
  • School profiles with Schema.org JSON-LD markup
  • Side-by-side comparison (/comparar/{a}-vs-{b})

02. Parent Dashboard (SPA)

  • Favorites, notes, browsing history
  • Child profiles with age-to-grade mapping
  • Saved searches with new match notifications
  • Personalized recommendations and reviews

03. School Dashboard

  • Profile and media management
  • Analytics: views, clicks, contact reveals
  • Comment moderation, email verification

04. Content & i18n

  • Blog, expat guides, education glossary
  • Full ES/EN support with hreflang tags
  • Cascading location picker (region → province → city → district)

// quality & devops

Testing

$ composer check
Laravel Pint ....... passed
PHPStan level 8 .... passed
PHPUnit 11 ........ 115 tests, 0 failures

CI/CD Pipeline

$ gh workflow view deploy
Detect changes ... front / parser
Lint + test ...... Pint, PHPStan, PHPUnit
Build assets .... npm run build
Deploy (rsync) .. zero-downtime
Notify .......... Telegram

Security

  • CSRF, rate limiting, SecurityHeaders middleware
  • Cloudflare Turnstile (CAPTCHA)
  • Google OAuth + email code verification
  • Form Request validation on all inputs

Infrastructure

  • Ansible playbooks for server provisioning
  • Caddy reverse proxy with auto-TLS
  • systemd services (parser, queue, scheduler)
  • Makefile for deploy, logs, DB sync

// tech stack

Backend
Laravel 12, PHP 8.4
Golang 1.24, GORM
PostgreSQL 16, Redis 7
Elasticsearch 8.x
Frontend
Vue 3, Inertia.js v2
TypeScript
Tailwind CSS 4
Blade, Vite 7
Admin & Auth
Filament v3 (59 resources)
Laravel Sanctum
Google OAuth
Turnstile CAPTCHA
Integrations
Google Places API
OpenAI API
Telegram Bot
Cloudflare Turnstile
$ wc -l --summary
30 models
59 filament resources
13/14 events/listeners
20+ Vue components
10+ Go packages