A comprehensive Go-based REST API backend designed to serve portfolio data through GitHub Gist integration. This backend provides endpoints for managing projects, experiences, feedback, and includes unique features like Baybayin transliteration and AI chat integration.
- RESTful API with clean endpoint structure
- GitHub Gist Integration for dynamic data management
- Baybayin Transliterator for Filipino script conversion
- AI Chat Agent powered by GPT-4o-mini via OpenRouter
- NGL Proxy Integration for anonymous messaging relay
- Three-tier Permission System (ALL, COOKIE, ADMIN)
- CORS Support with configurable origins
- Hot Reload Development with Air
- Comprehensive Logging with request tracking
- Environment-based Configuration for development and production
- Modular Architecture with clean separation of concerns
- Unicode Support for Baybayin script rendering
- Caching System for improved performance
- Resume Data Delivery via dedicated dev endpoint
- Project Management via admin-only endpoints
- Installation
- Configuration
- API Endpoints
- Documentation
- System Architecture
- Development
- Testing
- Deployment
- Troubleshooting
- Changelog
- Contributing
- License
- Go 1.25.3 or higher
- Git
- GitHub Personal Access Token (for Gist integration)
- OpenRouter API Key (for AI chat integration)
- Air for hot reload development (optional but recommended)
-
Clone the repository
git clone https://github.com/RyannKim327/portfolio-backend.git cd portfolio-backend -
Install dependencies
go mod download
-
Configure environment variables
Create a
.envfile in the root directory with the following variables:# Copy and modify these values according to your setup APP_ENV=development API_KEY=your_github_personal_access_token GIST_ID=your_gist_id_for_data_storage POST_API=your_post_api_key TG_API=your_telegram_bot_token TG_CHATID=your_telegram_chat_or_channel_id RAPIDKEY=your_rapidapi_key RAPIDHOST=youtube-mp36.p.rapidapi.com AI_API=your_openrouter_api_key PORT=8000 -
Run the application
# Development mode with hot reload air # Production mode go run index.go
Create a .env file in the root directory:
APP_ENV=development
API_KEY=your_github_personal_access_token
GIST_ID=your_gist_id_for_data_storage
POST_API=your_post_api_key
TG_API=your_telegram_bot_token
TG_CHATID=your_telegram_chat_or_channel_id
RAPIDKEY=your_rapidapi_key
RAPIDHOST=youtube-mp36.p.rapidapi.com
AI_API=your_openrouter_api_key
PORT=8000| Variable | Description | Required |
|---|---|---|
APP_ENV |
Application environment (development/production) | Yes |
API_KEY |
GitHub Personal Access Token for Gist API | Yes |
GIST_ID |
GitHub Gist ID for data storage | Yes |
POST_API |
API key read by X-API-Key header for admin-only POST requests |
Yes |
TG_API |
Telegram bot token used for /images proxying and /upload relays |
Yes (for images/uploads) |
TG_CHATID |
Telegram chat/channel ID that receives uploaded media | Yes (for uploads) |
RAPIDKEY |
RapidAPI key used by /yt |
Yes (for /yt) |
RAPIDHOST |
RapidAPI host used by /yt (e.g. youtube-mp36.p.rapidapi.com) |
Yes (for /yt) |
AI_API |
OpenRouter API key used for /ai/chat |
Yes (for /ai/chat) |
PORT |
Server port (defaults to 8000 if unset) | No |
| Method | Path | Permission | Description | Notes |
|---|---|---|---|---|
| GET | / |
ALL | Health/status probe | Returns application metadata and uptime markers. |
| GET | /projects |
ALL | Portfolio project listing | Reads from GitHub Gist; cached for 5m. |
| GET | /experiences |
ALL | Work experience timeline | Sorted chronologically before response. |
| GET | /certs |
ALL | Certificates listing | Reads certificates.json from GitHub Gist; reversed newest-first. |
| GET | /blog |
ALL | Blog feed | Streams entire blog.json, newest-first. |
| GET | /feedback |
ALL | Public feedback viewer | Supports pagination via page query. |
| GET | /poetry |
ALL | Poetry collection | Mirrors the curated poetry list from Gist. |
| GET | /baybayin |
ALL | Baybayin transliterator | Requires text query; returns Unicode script. |
| GET | /retrieve |
ALL | Telegram CDN proxy | Requires file (Telegram file_id). Proxies via Telegram Bot API. |
| GET | /manga |
ALL | Manga helper utility | Use s for search, r for series, c for chapter. |
| GET | /set-cookie |
ALL | Issues temporary cookie | Sets temporary cookie (30m) for cookie-protected access. |
| GET | /yt |
ALL | YouTube MP3 downloader helper | Requires videoID query. Uses RapidAPI. |
| GET | /dev |
ALL | Resume/Dev data retrieval | Retrieves resume.json from GitHub Gist. |
| GET | /contact |
ADMIN | Contact message list | Admin-only view of received messages. |
| POST | /feedback |
COOKIE | Stores feedback via Gist | Requires temporary cookie + JSON body. |
| POST | /contact |
COOKIE | Submits contact message | Requires temporary cookie. |
| POST | /poetry |
ADMIN | Publishes new poem entries | Requires X-API-Key header (matches POST_API). |
| POST | /ai/chat |
ALL | GPT-4o-mini chat relay | Accepts ChatGPT-style messages array via OpenRouter. |
| POST | /ngl |
ALL | NGL message proxy | Proxies anonymous messages to NGL.link. |
| POST | /blog |
ADMIN | Creates new blog post | Requires X-API-Key header. Auto-assigns ID. |
| POST | /certs |
ADMIN | Append a certificate entry | Requires X-API-Key header. |
| POST | /upload |
ADMIN | Telegram upload bridge | multipart/form-data with image field. |
| PUT | /blog |
ADMIN | Updates existing blog post | Requires X-API-Key header and post id. |
| PUT | /experiences |
ADMIN | Updates experience data | Requires X-API-Key header. Overwrites entire list. |
- Pagination:
pagequery (β₯ 1, default1). - Cache: Responses cached for 5 minutes; requesting page
1invalidates the cache first. - Storage: Reads
feedback.jsonfrom the configured GitHub Gist.
curl "http://localhost:8000/feedback?page=2"- Pagination:
pagequery (β₯ 1, default1). Response includespages,current,count, anddata. - Cache: Responses cached for 5 minutes and refreshed in the background when cache is valid.
- Ordering: Newest entries appear first by reversing the list in-memory.
curl "http://localhost:8000/blog?page=1"- Usage: Converts Latin text to Baybayin script using Unicode glyphs and normalization.
- Tip: Strip punctuation on the client side for best transliteration accuracy.
curl "http://localhost:8000/baybayin?text=kumusta%20ka"GET /manga is a multi-mode scraper around a third-party manga source:
- Search β
?s=<title>to get matching series metadata. - Chapter List β
?r=<series-slug>to enumerate chapters. - Chapter Pages β
?r=<series-slug>&c=<chapter-id>to receive page image URLs.
- Input:
filequery takes a Telegramfile_idreturned byPOST /upload. - Behaviour: The backend downloads the file via Telegram Bot API and streams it to the caller, masking bot credentials.
curl "http://localhost:8000/retrieve?file=AgACAgUAAxkBAAIBQWdow"- Body: Chat-style payload with
messagesarray. - Model: Uses
openai/gpt-4o-minivia OpenRouter. - Headers: Requires
AI_APIenvironment variable for authorization.
curl -X POST http://localhost:8000/ai/chat \\
-H "Content-Type: application/json" \\
-d '{
"messages": [
{"role": "user", "content": "Hello, how are you?"}
]
}'- Body: JSON payload with
usernameandquestion. - Behavior: Proxies the submission to NGL.link API.
curl -X POST http://localhost:8000/ngl \\
-H "Content-Type: application/json" \\
-d '{
"username": "example_user",
"question": "Ask me anything!"
}'- Security: Requires both
X-API-Keyheader (POST_API) and valid Telegram env vars. - Response: Forwards Telegram's JSON payload, including the generated
file_idfor re-use with/retrieve.
curl -X POST http://localhost:8000/upload \\
-H "X-API-Key: $POST_API" \\
-F "image=@/path/to/photo.jpg"The README doubles as the living reference, but the project ships with several complementary documentation touchpoints:
- Route metadata: Each handler exports a
utils.Routedefinition underendpoints/getorendpoints/post, making it easy to inspect path, method, and permission levels directly in code. - Environment reference:
.env(sample) plus the Configuration section lists every supported variable. - Middleware contracts:
middleware/contains concise, self-documented functions that describe headers, cookies, and permission checks.
- Go Doc β run
go doc ./...to generate inline package documentation for handlers, middleware, and utilities. - cURL/HTTP collections β the snippets in this README can be pasted into REST clients (Hoppscotch, Thunder Client, Postman) for quick smoke tests.
- Ad-hoc OpenAPI β if you maintain a
docs/openapi.yaml, regenerate it after adding routes by iterating through theutils.Routelist; the structure was designed with spec generation in mind.
- Update tables under API Endpoints whenever a handler is added/changed.
- Keep diagrams (Mermaid) in the System Architecture section synchronized with actual dependencies (Gist, Telegram, Pollinations, cache).
- Mention schema or payload tweaks in the Changelog so clients know when to adapt.
- Gin Router: Terminates HTTP traffic, applies CORS/default headers, and dispatches into the routing matrix declared in
endpoints/. - Permission Tier: Unified middleware enforces
ALL,COOKIE, orADMINaccess levels before any handler executes business logic. - Handler Layer: Consolidates response shaping, cache orchestration, and fan-out to third-party services such as GitHub Gist, OpenRouter (GPT), and Telegram Bot API.
- Caching Strategy:
- Global Gist Cache: In-memory cache for all Gist
GETrequests with a 5-minute TTL, implemented inutils/gist.go. - Endpoint-specific Cache: Specialized caching for
/feedbackand/blogto handle pagination and high-traffic needs.
- Global Gist Cache: In-memory cache for all Gist
- Utility Processors: Baybayin transliteration, manga scraping, and Gist integration helpers live in
utils/.
flowchart TD
A[Client Apps:<br /> Web, Mobile, CLI] --> B[Gin Router]
B --> C{Permission Tier}
C -->|ALL| D[Handlers]
C -->|COOKIE| E[Cookie Middleware]
C -->|ADMIN| F[Admin Middleware]
E --> D
F --> D
D --> G{Cache Hit?}
G -->|Yes| Q[JSON Response]
G -->|No| H{Data Source}
H -->|Portfolio & Content| I[GitHub Gist API]
H -->|AI Chat| J[OpenRouter / GPT-4o-mini]
H -->|Media Bridge| K[Telegram Bot API]
H -->|Scrapers & Local Ops| L[Internal Processors<br/>Baybayin, Manga, NGL Proxy, etc.]
I --> M[Normalizer + Cache Writer]
J --> M
| Component | Responsibility | Notes |
|---|---|---|
Router (index.go) |
Initializes Gin, mounts CORS/Headers, and registers routes from endpoints.Routes |
Uses mw.Register to apply permission gating |
Middleware (middleware/) |
Enforces permission tiers (ALL, COOKIE, ADMIN), sets headers, and logs requests |
Centralizes security and observability |
Handlers (endpoints/) |
Business logic, payload binding, and response formatting | Divided into get/, post/, and put/ packages |
| Cache Layer | In-memory storage with TTL (default 5m) for Gist reads | Reduces GitHub API rate-limit consumption |
| External Services | GitHub Gist (Storage), OpenRouter (AI), Telegram (Media Storage), RapidAPI (YouTube DL) | Isolated via utils for modularity |
Utilities (utils/) |
Shared structures, Gist API clients, and local script processors | Enforces DRY principles across handlers |
flowchart LR
A[portfolio-backend]
A --> B[endpoints/]
A --> C[middleware/]
A --> D[utils/]
A --> E[index.go]
A --> F[go.mod]
B --> B1[get/]
B --> B2[post/]
B --> B3[put/]
B --> B4[index.go]
C --> C1[headers.go]
C --> C2[cookie_handler.go]
C --> C3[post_request.go]
C --> C4[server_handler.go]
D --> D1[gist.go]
D --> D2[gist_handler.go]
D --> D3[structures.go]
D --> D4[statics.go]
D --> D5[tools.go]
Notes:
endpoints/contains route definitions grouped by HTTP method (get/,post/,put/).utils/contains Gist clients, shared structs, constants, and local processors.middleware/enforces headers, auth tiers, and request handling concerns.
sequenceDiagram
participant C as Client
participant R as Router
participant M as Middleware
participant H as Handler
participant Cache as Cache Layer
participant G as GitHub Gist
participant T as Telegram API
participant AI as OpenRouter API
participant S as Scrapers/Utils
C->>R: HTTP Request
R->>M: Route + attach metadata
M->>H: Enforce permissions & forward
H->>Cache: Lookup composite cache key
alt Cache Hit
Cache-->>H: Cached payload
else Cache Miss
H->>G: Fetch portfolio/feedback data
H->>AI: Proxy chat payloads
H->>T: Relay uploads/fetch files
H->>S: Execute Baybayin/manga/yt/ngl logic
G-->>H: JSON blobs
AI-->>H: AI responses
T-->>H: Telegram payloads
S-->>H: Processed data
H-->>Cache: Store normalized response
end
H->>C: JSON Response
This project uses Air for hot reload during development:
# Install Air (if not already installed)
go install github.com/cosmtrek/air@latest
# Start development server with hot reload
air
# The server will automatically restart when you make changes to .go files
# Build logs are saved to build-errors.log
# Temporary files are stored in the tmp/ directory# Build the application
go build -o portfolio-backend .
# Run the built binary
./portfolio-backend
# Or build and run in one command
go run index.goportfolio-backend/
βββ endpoints/ # API endpoint definitions
β βββ get/ # GET request handlers
β βββ post/ # POST request handlers
β βββ put/ # PUT request handlers
β βββ index.go # Route registration
βββ middleware/ # HTTP middleware
β βββ server_handler.go
β βββ headers.go
β βββ cookie_handler.go
β βββ post_request.go
βββ utils/ # Utility functions
β βββ structures.go # Data structures
β βββ gist_handler.go # GitHub Gist integration
β βββ gist.go # Gist client & global cache
β βββ statics.go # Constants
β βββ tools.go # Helper functions
βββ tmp/ # Temporary files (Air)
βββ .air.toml # Air configuration
βββ .env # Environment variables
βββ go.mod # Go module definition
βββ index.go # Application entry point
- Create endpoint file in
endpoints/get/orendpoints/post/ - Define route structure using
utils.Route - Register route in
endpoints/index.go - Implement handler function
Example:
// endpoints/get/example.go
package get
import (
"portfolio-backend/utils"
"github.com/gin-gonic/gin"
)
var Example = utils.Route{
Path: "/example",
Method: utils.METHOD_GET,
Permission: utils.PERMISSION_ALL, // or PERMISSION_COOKIE, PERMISSION_ADMIN
Handler: func(ctx *gin.Context) {
ctx.JSON(200, gin.H{
"message": "Hello World",
})
},
}Then add it to endpoints/index.go:
var Routes = []utils.Route{
// ... existing routes
get.Example, // Add your new route here
}The application supports three permission levels:
PERMISSION_ALL: Open access, no authentication requiredPERMISSION_COOKIE: Requires valid cookie authenticationPERMISSION_ADMIN: Requires admin-level authentication
You can test the API endpoints using curl or any HTTP client:
# Test server status
curl http://localhost:8000/
# Test Baybayin transliterator
curl "http://localhost:8000/baybayin?text=kumusta ka"
# Test AI chat (POST request)
curl -X POST http://localhost:8000/ai/chat \
-H "Content-Type: application/json" \
-d '{"messages":[{"role":"user","content":"Hello!"}]}'
# Test NGL proxy (POST request)
curl -X POST http://localhost:8000/ngl \
-H "Content-Type: application/json" \
-d '{"username":"ryannkim327","question":"Hello from CLI!"}'
# Test feedback submission (requires cookie)
curl -X POST http://localhost:8000/feedback \
-H "Content-Type: application/json" \
-H "Cookie: temporary=your-temporary-cookie" \
-d '{"name":"Test","email":"test@example.com","message":"Test message"}'# Test with different environments
APP_ENV=production go run index.go
APP_ENV=development go run index.go-
Build the application
go build -o portfolio-backend . -
Set production environment variables
export APP_ENV=production export API_KEY=your_production_github_token export GIST_ID=your_production_gist_id export POST_API=your_production_post_api_key export TG_API=your_production_telegram_bot_token export TG_CHATID=your_production_telegram_chat_id export AI_API=your_production_openrouter_api_key export PORT=8000
-
Run the application
./portfolio-backend
Create a Dockerfile:
FROM golang:1.25-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -o portfolio-backend .
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/portfolio-backend .
EXPOSE 8000
CMD ["./portfolio-backend"]- Set
APP_ENV=productionfor production optimizations - Use secure API keys and tokens
- Configure appropriate CORS origins
- Set up proper logging and monitoring
# Check what's using port 8000
lsof -i :8000
# Kill the process or change PORT in .env
export PORT=8080- Ensure your GitHub Personal Access Token has proper permissions
- Check rate limits:
curl -H "Authorization: token YOUR_TOKEN" https://api.github.com/rate_limit
- Check your CORS configuration in the middleware
- Ensure your frontend origin is allowed
- Verify your
GIST_IDis correct - Ensure your GitHub token has gist permissions
- Check if the gist is public or private
# Clean module cache
go clean -modcache
# Reinstall dependencies
go mod download
# Verify Go version
go versionEnable debug logging by setting:
export APP_ENV=development- Air build logs:
build-errors.log - Application logs: Check console output
- Request logs: Enabled by default in development mode
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the Project
- Create your Feature Branch (
git checkout -b feature/AmazingFeature) - Commit your Changes (
git commit -m 'Add some AmazingFeature') - Push to the Branch (
git push origin feature/AmazingFeature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE.md file for details.