A blog with a Praia REST API backend, PostgreSQL database, and Vue.js frontend.
docker compose upThis starts PostgreSQL, the Praia backend (port 3000), and the Nginx frontend (port 8080). Open http://localhost:8080.
Start PostgreSQL:
docker compose up db -dInstall backend dependencies and start the server:
cd backend
sand install
cp .env.example .env # edit if needed
praia main.praiaIn a separate terminal, start the frontend dev server:
cd frontend
npm install
npm run devOpen http://localhost:5173. The Vite dev server proxies /api requests to the Praia backend.
blog/
├── docker-compose.yml
├── backend/
│ ├── main.praia # entry point — routes, middleware, server
│ ├── .env # DB credentials (not committed)
│ ├── sand-lock.yaml # grain dependencies
│ ├── migrations/ # SQL migrations (-- up / -- down)
│ │ └── 001_create_posts.sql
│ └── grains/
│ ├── posts.praia # CRUD data layer
│ ├── utils.praia # slug generation
│ └── migrate.praia # migration runner
└── frontend/
├── vite.config.js # proxies /api to backend
├── nginx.conf # production proxy + SPA fallback
└── src/
├── api/posts.js # API client
├── views/ # Home, Post, Admin, Editor, NotFound
└── components/ # Header, Footer, PostCard, MarkdownContent
| Method | Path | Description |
|---|---|---|
| GET | /api/posts |
List published posts (?all=true for drafts) |
| GET | /api/posts/:slug |
Get post by slug |
| POST | /api/posts |
Create post |
| PUT | /api/posts/:id |
Update post |
| DELETE | /api/posts/:id |
Delete post |
Migration files live in backend/migrations/ with -- up and -- down sections:
-- up
CREATE TABLE posts ( ... );
-- down
DROP TABLE posts;Migrations run automatically on server startup. To check status or rollback manually:
praia -c 'use "db"; use "./grains/migrate"; let pg = db.postgres({...}); migrate.status(pg, "./migrations")'
cd frontend && npm run buildThe backend serves the built files from ../frontend/dist/ automatically.
MIT