Skip to content

gRPC API Documentation

Last Updated: 2025-11-23

This document describes the gRPC API implementation in ocmonica using ConnectRPC, including service definitions, protocol support, and client usage.


Table of Contents


Overview

Ocmonica's gRPC API is built with ConnectRPC v1.19.1 and provides type-safe, high-performance RPC endpoints for modern web apps and native clients.

Key Features: - ✅ Compile-time type safety from protobuf schemas - ✅ Shared types between backend and frontend - ✅ Automatic serialization (no manual JSON marshalling) - ✅ Binary Protocol Buffers (smaller payload) - ✅ HTTP/2 multiplexing - ✅ Server streaming (SearchStream) - ✅ Built-in compression

Base Path: /ocmonica.v1.*/*


Protocol Support

ConnectRPC supports three protocols on the same endpoint:

  • JSON or binary encoding
  • Browser-friendly (uses standard HTTP/POST)
  • Best for web applications
  • Easy to debug with JSON format

Example:

const transport = createConnectTransport({
  baseUrl: 'http://localhost:8080',
  useBinaryFormat: false,  // JSON format for debugging
});

2. gRPC Protocol

  • Binary Protocol Buffers encoding
  • Requires HTTP/2
  • Best for server-to-server communication
  • Smallest payload size

Example:

const transport = createConnectTransport({
  baseUrl: 'http://localhost:8080',
  useBinaryFormat: true,  // Binary protobuf
});

3. gRPC-Web Protocol

  • Compatible with gRPC proxies (e.g., Envoy)
  • Works with existing gRPC infrastructure
  • Supports browser clients

Authentication

gRPC API uses JWT authentication via interceptors, similar to REST middleware.

gRPC Authentication (internal/api/grpc/interceptors/auth.go):

// Interceptor validates JWT token from Authorization header
func NewAuthInterceptor(tokenService *service.TokenService) connect.UnaryInterceptorFunc {
    return func(next connect.UnaryFunc) connect.UnaryFunc {
        return func(ctx context.Context, req connect.AnyRequest) (connect.AnyResponse, error) {
            token := extractBearerToken(req.Header())
            claims, err := tokenService.ValidateToken(token)
            // ... adds claims to context
        }
    }
}

Using Tokens

Include the JWT access token in request headers:

const transport = createConnectTransport({
  baseUrl: 'http://localhost:8080',
  interceptors: [
    (next) => async (req) => {
      req.header.set('Authorization', `Bearer ${accessToken}`);
      return await next(req);
    }
  ]
});

Note: Obtain access tokens via REST API login endpoint (POST /api/v1/auth/login).


Available Services

FileService

Implementation: internal/api/grpc/file_service.go Protobuf: proto/file/v1/file_service.proto

Methods: - UploadFile - Upload a new file - DownloadFile - Download file content - GetFile - Get file metadata - ListFiles - List files in directory - DeleteFile - Delete a file - MoveFile - Move file to different directory - CopyFile - Copy file to different location - UpdateFile - Update file metadata

Status: ✅ Complete parity with REST API

DirectoryService

Implementation: internal/api/grpc/directory_service.go Protobuf: proto/directory/v1/directory_service.proto

Methods: - CreateDirectory - Create new directory - GetDirectory - Get directory metadata - ListDirectories - List subdirectories - DeleteDirectory - Delete directory - MoveDirectory - Move directory

Status: ✅ Complete parity with REST API

SearchService

Implementation: internal/api/grpc/search_service.go Protobuf: proto/search/v1/search_service.proto

Methods: - Search - Search files and directories (single response) - SearchStream - Search with server streaming (gRPC advantage)

Status: ✅ Complete with additional streaming capability

Server Streaming Example:

const stream = client.searchStream({ query: 'report' });

for await (const result of stream) {
  console.log('Found:', result.name);
}

GroupService

Implementation: internal/api/grpc/group_service.go Protobuf: proto/group/v1/group_service.proto

Methods: - CreateGroup - Create new group - GetGroup - Get group details - ListGroups - List all groups - UpdateGroup - Update group metadata - DeleteGroup - Delete group - AddMember - Add user to group - RemoveMember - Remove user from group - ListMembers - List group members

Status: ✅ Complete parity with REST API

WatchedFolderService

Implementation: internal/api/grpc/watched_folder_service.go Protobuf: proto/watched_folder/v1/watched_folder_service.proto

Methods: - CreateWatchedFolder - Create watched folder - GetWatchedFolder - Get watched folder details - ListWatchedFolders - List watched folders - UpdateWatchedFolder - Update watched folder - DeleteWatchedFolder - Delete watched folder - TriggerScan - Manually trigger folder scan - GetIndexedFiles - Get files indexed from folder

Status: ✅ Complete parity with REST API


Authorization (RBAC)

Both REST and gRPC APIs enforce RBAC via similar patterns.

gRPC Interceptor (internal/api/grpc/interceptors/permission.go):

func NewPermissionInterceptor(permissionService *service.PermissionService, requiredPermission string) connect.UnaryInterceptorFunc {
    return func(next connect.UnaryFunc) connect.UnaryFunc {
        return func(ctx context.Context, req connect.AnyRequest) (connect.AnyResponse, error) {
            if !permissionService.HasPermission(ctx, requiredPermission) {
                return nil, connect.NewError(connect.CodePermissionDenied, errors.New("permission denied"))
            }
            return next(ctx, req)
        }
    }
}

Permission Format: resource:action (e.g., file:read, file:write, org:admin)

ConnectRPC Error Codes: - connect.CodeNotFound - 404 - connect.CodePermissionDenied - 403 - connect.CodeUnauthenticated - 401 - connect.CodeInvalidArgument - 400 - connect.CodeInternal - 500


Code Generation

Backend (Go)

Configuration: buf.gen.yaml

version: v2
plugins:
  - remote: buf.build/connectrpc/go:v1.19.1
    out: gen
    opt:
      - paths=source_relative
  - remote: buf.build/protocolbuffers/go:v1.36.10
    out: gen
    opt:
      - paths=source_relative

Generates: - gen/ocmonica/v1/*_pb.go - Message types - gen/ocmonica/v1/*_connect.go - Service interfaces and clients

Command: task proto:gen

Frontend (TypeScript)

Configuration: web/buf.gen.yaml

version: v2
plugins:
  - remote: buf.build/connectrpc/es:v2.9.0
    out: src/gen
    opt:
      - target=ts
  - remote: buf.build/protocolbuffers/es:v2.9.0
    out: src/gen
    opt:
      - target=ts

Generates: - src/gen/ocmonica/v1/*_pb.ts - Message types - src/gen/ocmonica/v1/*_connect.ts - Service clients

Command: task web:gen


Client Usage

TypeScript/React Example

Setup Transport:

import { createConnectTransport } from '@connectrpc/connect-web';
import { createPromiseClient } from '@connectrpc/connect';
import { FileService } from '@/gen/ocmonica/v1/file_service_connect';

const transport = createConnectTransport({
  baseUrl: 'http://localhost:8080',
  useBinaryFormat: false,
});

const client = createPromiseClient(FileService, transport);

Upload File:

const response = await client.uploadFile({
  filename: 'document.pdf',
  content: fileBuffer,
  directoryId: 'dir-123',
});

console.log('Uploaded:', response.file);

List Files:

const response = await client.listFiles({
  directoryId: 'dir-123',
  limit: 20,
});

console.log('Files:', response.files);

Stream Search Results:

const stream = client.searchStream({ query: 'report' });

for await (const result of stream) {
  console.log('Found:', result.file.name);
}

With TanStack Query

import { useQuery } from '@tanstack/react-query';

function FileList({ directoryId }: { directoryId: string }) {
  const { data, isLoading } = useQuery({
    queryKey: ['files', directoryId],
    queryFn: () => client.listFiles({ directoryId }),
  });

  if (isLoading) return <div>Loading...</div>;

  return (
    <ul>
      {data?.files.map(file => (
        <li key={file.id}>{file.name}</li>
      ))}
    </ul>
  );
}

Key Differences from REST

Type Safety

gRPC (ConnectRPC): - ✅ Compile-time type safety from protobuf schemas - ✅ Shared types between backend and frontend - ✅ Automatic serialization (no manual JSON marshalling) - ✅ Breaking changes caught at build time

REST: - ⚠️ Manual type definitions in both Go and TypeScript - ⚠️ Runtime errors for type mismatches - ⚠️ Manual JSON marshalling/unmarshalling - ⚠️ API drift between backend and frontend possible

Performance

gRPC (ConnectRPC): - ✅ Binary Protocol Buffers (smaller payload) - ✅ HTTP/2 multiplexing (multiple requests on one connection) - ✅ Server streaming (SearchStream) - ✅ Built-in compression

REST: - ⚠️ JSON encoding (larger payload) - ⚠️ HTTP/1.1 or HTTP/2 - ❌ No streaming support - ⚠️ Manual compression configuration

Developer Experience

gRPC (ConnectRPC): - ✅ Auto-generated clients (no manual API client code) - ✅ IDE autocomplete for all methods and types - ✅ Protobuf docs embedded in generated code - ⚠️ Requires protobuf knowledge - ⚠️ Additional build step (code generation)

REST: - ✅ Simple HTTP/JSON (universally understood) - ✅ Easy to test with curl/Postman - ✅ No build steps for clients - ⚠️ Manual client code - ⚠️ OpenAPI/Swagger needed for docs

Client Compatibility

gRPC (ConnectRPC): - ✅ Modern web browsers (Connect protocol) - ✅ Node.js clients - ✅ Go clients - ✅ gRPC clients (via gRPC protocol) - ⚠️ Requires generated client libraries

REST: - ✅ Any HTTP client (curl, wget, browsers, mobile apps) - ✅ No client library required - ✅ Universal compatibility - ✅ Works with legacy systems


Feature Gaps

Not Available via gRPC

The following endpoints are REST-only:

  1. Authentication Management
  2. User registration, login, logout
  3. Token refresh
  4. Profile management
  5. Password changes

  6. Organization Management

  7. Create/update/delete organizations
  8. Organization membership

  9. Role Management (RBAC)

  10. Create/update/delete roles
  11. Role assignment

  12. API Key Management

  13. Create/revoke API keys

  14. Health & Monitoring

  15. Health checks (/health)
  16. Prometheus metrics (/metrics)

Recommendation: Use REST API for authentication and administrative tasks. Use gRPC API for file operations and core application features.


See Also