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.

EndpointMethodRequest BodyDescription
/auth/loginPOST{ email, password }Authenticates via Keycloak ROPC, creates Redis session, sets session_id cookie. Returns { success, user }.
/auth/registerPOST{ email, password, firstName?, lastName? }Creates user via Keycloak Admin API, auto-logs in via ROPC, sets cookie. Returns { success, user } (201).
/auth/logoutPOSTClears 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/*.

EndpointMethodDescription
/uploadsPOSTUpload an image file (multipart, auth required, 5MB limit). Returns { key }.
/uploads/:keyGETServe an uploaded file (public, cached).

Message Patterns

All message patterns are defined in packages/shared/src/types/index.ts.

Core Service (service-core)

PatternDescription
core.getItemsList items (paginated)
core.getItemGet single item by ID
core.createItemCreate a new item
core.updateItemUpdate an existing item
core.deleteItemDelete an item
core.getProfileGet user profile by userId
core.getOrCreateProfileGet or lazily create profile for a user
core.updateProfileUpdate profile fields (headline, location, bio, etc.)
core.getPublicProfileGet public portfolio (profile + projects + skills + experience)
core.getProjectsList projects for a user
core.createProjectCreate a portfolio project
core.updateProjectUpdate a portfolio project
core.deleteProjectDelete a portfolio project
core.reorderProjectsReorder portfolio projects
core.getSkillsList skills for a user
core.createSkillAdd a skill
core.updateSkillUpdate a skill
core.deleteSkillDelete a skill
core.reorderSkillsReorder skills
core.getExperiencesList experience entries for a user
core.createExperienceAdd work experience
core.updateExperienceUpdate work experience
core.deleteExperienceDelete work experience
core.reorderExperiencesReorder experience entries
core.createSocialLinkCreate a social link for a user
core.updateSocialLinkUpdate a social link
core.deleteSocialLinkDelete a social link
core.reorderSocialLinksReorder social links
core.getSkillCategoriesList skill categories for a user
core.createSkillCategoryCreate a skill category
core.updateSkillCategoryUpdate a skill category
core.deleteSkillCategoryDelete 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"

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
    }
  }
}
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
    }
  }
}
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"]
  }
}
mutation CreateSkill($input: CreateSkillInput!) {
  createSkill(input: $input) {
    id
    name
    category
    proficiency
  }
}

Variables:

{
  "input": {
    "name": "TypeScript",
    "categoryId": "category-uuid",
    "proficiency": "ADVANCED"
  }
}
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"
  }
}