API Reference
GraphQL Endpoint
- URL:
/graphql(via API gateway, routed by Next.js rewrites locally or K8s ingress in production) - Playground: Available in development at
http://localhost:3001/graphql - Schema: Auto-generated (code-first via NestJS + Apollo Server)
Authentication
All protected GraphQL operations require a valid session_id cookie. The cookie is set automatically by POST /auth/login or POST /auth/register after successful authentication. The browser sends it on every request via credentials: 'include'.
The API Gateway’s SessionGuard looks up the session in Redis, verifies it is still valid, refreshes the access token if near expiry, and injects { userId, roles } into the GraphQL context. Requests without a valid session receive a 401 Unauthorized response.
No Authorization header is needed. Authentication is fully cookie-based.
REST Auth Endpoints
These endpoints are served by the API Gateway at /auth/*. The gateway authenticates against Keycloak server-side using ROPC (login) and the Admin REST API (registration). No browser redirect to Keycloak occurs.
| Endpoint | Method | Request Body | Description |
|---|---|---|---|
/auth/login | POST | { email, password } | Authenticates via Keycloak ROPC, creates Redis session, sets session_id cookie. Returns { success, user }. |
/auth/register | POST | { email, password, firstName?, lastName? } | Creates user via Keycloak Admin API, auto-logs in via ROPC, sets cookie. Returns { success, user } (201). |
/auth/logout | POST | — | Clears Redis session and cookie. Returns { success: true }. |
REST Upload Endpoints
File uploads use REST (multipart/form-data) instead of GraphQL. These endpoints are served by the API Gateway at /uploads/*.
| Endpoint | Method | Description |
|---|---|---|
/uploads | POST | Upload an image file (multipart, auth required, 5MB limit). Returns { key }. |
/uploads/:key | GET | Serve an uploaded file (public, cached). |
Message Patterns
All message patterns are defined in packages/shared/src/types/index.ts.
Core Service (service-core)
| Pattern | Description |
|---|---|
core.getItems | List items (paginated) |
core.getItem | Get single item by ID |
core.createItem | Create a new item |
core.updateItem | Update an existing item |
core.deleteItem | Delete an item |
core.getProfile | Get user profile by userId |
core.getOrCreateProfile | Get or lazily create profile for a user |
core.updateProfile | Update profile fields (headline, location, bio, etc.) |
core.getPublicProfile | Get public portfolio (profile + projects + skills + experience) |
core.getProjects | List projects for a user |
core.createProject | Create a portfolio project |
core.updateProject | Update a portfolio project |
core.deleteProject | Delete a portfolio project |
core.reorderProjects | Reorder portfolio projects |
core.getSkills | List skills for a user |
core.createSkill | Add a skill |
core.updateSkill | Update a skill |
core.deleteSkill | Delete a skill |
core.reorderSkills | Reorder skills |
core.getExperiences | List experience entries for a user |
core.createExperience | Add work experience |
core.updateExperience | Update work experience |
core.deleteExperience | Delete work experience |
core.reorderExperiences | Reorder experience entries |
core.createSocialLink | Create a social link for a user |
core.updateSocialLink | Update a social link |
core.deleteSocialLink | Delete a social link |
core.reorderSocialLinks | Reorder social links |
core.getSkillCategories | List skill categories for a user |
core.createSkillCategory | Create a skill category |
core.updateSkillCategory | Update a skill category |
core.deleteSkillCategory | Delete a skill category |
Response Format
All microservice responses use ServiceResponse<T>:
{
success: boolean;
data?: T;
error?: string;
message?: string;
}
Pagination
Paginated endpoints accept page and limit parameters and return:
{
items: T[];
total: number;
}
GraphQL Examples
Health check
query {
health
}
Expected: "API Gateway is running"
Current user (requires session cookie)
The me query returns the authenticated user’s profile. On first call, a profile is lazily created from session data (userId, email, name from Keycloak).
query Me {
me {
userId
email
name
roles
firstName
lastName
avatarUrl
bio
headline
location
socialLinks {
id
platform
url
label
sortOrder
}
}
}
Update profile (requires session cookie)
mutation UpdateProfile($input: UpdateProfileInput!) {
updateProfile(input: $input) {
userId
email
firstName
lastName
avatarUrl
bio
headline
location
}
}
Variables:
{
"input": {
"firstName": "John",
"lastName": "Doe",
"bio": "Software engineer",
"headline": "Full-stack developer",
"location": "San Francisco, CA"
}
}
Get items
query GetItems($page: Float = 1, $limit: Float = 10) {
getItems(page: $page, limit: $limit) {
items {
id
name
description
createdAt
}
total
}
}
Create item
mutation CreateItem($name: String!, $description: String) {
createItem(name: $name, description: $description) {
id
name
description
createdAt
}
}
Get public profile (no auth required)
Returns a user’s full portfolio including projects, skills, and experience. Accessible at /u/[userId].
query GetPublicProfile($userId: String!) {
getPublicProfile(userId: $userId) {
userId
firstName
lastName
avatarUrl
bio
headline
location
socialLinks {
id
platform
url
label
sortOrder
}
projects {
id
title
description
liveUrl
repoUrl
tags
sortOrder
}
skills {
id
name
category {
id
name
}
proficiency
sortOrder
}
experiences {
id
company
role
description
startDate
endDate
sortOrder
}
}
}
Create project (requires session cookie)
mutation CreateProject($input: CreateProjectInput!) {
createProject(input: $input) {
id
title
description
tags
sortOrder
}
}
Variables:
{
"input": {
"title": "My App",
"description": "A cool project",
"liveUrl": "https://myapp.com",
"repoUrl": "https://github.com/me/myapp",
"tags": ["React", "TypeScript"]
}
}
Create skill (requires session cookie)
mutation CreateSkill($input: CreateSkillInput!) {
createSkill(input: $input) {
id
name
category
proficiency
}
}
Variables:
{
"input": {
"name": "TypeScript",
"categoryId": "category-uuid",
"proficiency": "ADVANCED"
}
}
Create experience (requires session cookie)
mutation CreateExperience($input: CreateExperienceInput!) {
createExperience(input: $input) {
id
company
role
startDate
endDate
}
}
Variables:
{
"input": {
"company": "Acme Corp",
"role": "Senior Developer",
"description": ["Built scalable microservices", "Led team of 5 engineers"],
"startDate": "2023-01-01T00:00:00Z"
}
}