Skip to content

Instantly share code, notes, and snippets.

View bogdan's full-sized avatar
💭
Temporary enslaved by prompt

Bogdan Gusiev bogdan

💭
Temporary enslaved by prompt
View GitHub Profile

How We Keep Our API and Frontend Automatically In Sync

Our backend is a Ruby REST API built with Grape. Our frontend is TypeScript. Keeping them in sync manually is a recipe for silent drift — a renamed field here, a removed param there, and suddenly the frontend is sending requests the server no longer understands or rendering fields that no longer exist.

This post describes the pipeline we built so that the server code is the single source of truth, type mismatches are caught before they ship, and backward incompatibilities surface as compiler errors rather than production bugs. The specific tools are Ruby-flavored, but the pattern is language-agnostic and maps directly to FastAPI, NestJS, or any framework that can generate an OpenAPI spec from code.


The Pipeline at a Glance

<script lang="ts">
import * as Routes from '../routes';
import { Image, Info } from '@lucide/svelte';
export interface Face {
id: number;
oval: { cx: number; cy: number; rx: number; ry: number; angle: number };
approved_with_default: boolean;
}
[ActiveJob] [ObservaImport::ImportJob] [119ae6b0-f942-4fc1-96fa-f3b18972e1ec] HTTP GET (13617.91ms) https://observaprodstorage.blob.core.windows.net:443/observa-observations-media/obs_1078807_13681178.jpg
[ActiveJob] [ObservaImport::ImportJob] [35435437-bd07-4261-aa42-5c9f13696f45] HTTP GET (11262.95ms) https://observaprodstorage.blob.core.windows.net:443/observa-observations-media/obs_1078807_13681186.jpg
[ActiveJob] [ObservaImport::ImportJob] [44142748-fda1-458f-ad6c-b49e812c5ba8] HTTP GET (828.76ms) https://observaprodstorage.blob.core.windows.net:443/observa-observations-media/obs_1078807_13681183.jpg
[ActiveJob] [ObservaImport::ImportJob] [5ed1a017-fbdf-4590-9b9b-ac7053c34a45] HTTP GET (10425.22ms) https://observaprodstorage.blob.core.windows.net:443/observa-observations-media/obs_1078807_13681185.jpg
[ActiveJob] [ObservaImport::ImportJob] [72b38b9f-487f-4cc3-9497-8f89f91dc2a9] HTTP GET (2801.01ms) https://observaprodstorage.blob.core.windows.net:443/observa-observations-media/obs_107
# frozen_string_literal: true
require "date"
require "hexapdf"
require "pathname"
require "stringio"
require "tempfile"
require 'furi'
module PDFUtils
MIME_TYPE = "application/pdf"

Here’s a minimal, idiomatic Rails example wiring Current.organization into your routes and URL generation using your scoped routes.


1. Current

class Current < ActiveSupport::CurrentAttributes
  attribute :organization
end
Here’s a **minimal, idiomatic Rails example** wiring **`Current.organization`** into your routes and URL generation using your scoped routes.
---
## 1. `Current`
```ruby
class Current < ActiveSupport::CurrentAttributes
attribute :organization
end
Here’s a **minimal, idiomatic Rails example** wiring **`Current.organization`** into your routes and URL generation using your scoped routes.
---
## 1. `Current`
```ruby
class Current < ActiveSupport::CurrentAttributes
attribute :organization
end
@bogdan
bogdan / qq
Created November 1, 2025 19:46
1BRrGeA1mO9N4ZtJFj24uoDAQiRPn5FAl bafkreicykfjup4ete2d25hrvm7ys3vl3wbge6gv2wdb27wxbfru3ildtou.heic image/heif 35872 mwdm1k5QLBuWqVkTGRAqkA== 806 604 2025-10-01T08:47:29.000Z https://drive.google.com/uc?id=1BRrGeA1mO9N4ZtJFj24uoDAQiRPn5FAl&export=download
1OAdZogAIRWmb5OwgzNkc7MiYIIKzfjPS AY2A5116.jpg image/jpeg 146701 RlN236aHfMu7HVFSy74YUg== 1024 683 2024-08-05T16:05:17.000Z https://drive.google.com/uc?id=1OAdZogAIRWmb5OwgzNkc7MiYIIKzfjPS&export=download
1m7XT4b6MVklws1JZ1F2R5fbadyi1PO34 AY2A5096.jpg image/jpeg 312152 w1GQW05j/c/bGMq+gDmkQg== 1024 683 2024-08-05T16:05:13.000Z https://drive.google.com/uc?id=1m7XT4b6MVklws1JZ1F2R5fbadyi1PO34&export=download
1JrVR4GMe70LJUXuEOalu7Jir97W54mH_ IMG_7606.jpg image/jpeg 430681 2PQEsT8HxOobgwvF20TyHg== 2048 1536 2022-04-05T15:23:17.000Z https://drive.google.com/uc?id=1JrVR4GMe70LJUXuEOalu7Jir97W54mH_&export=download
15R4Bp0Qlu1FwuCUh6xMrtUNzhWd4wTnd IMG_7611.jpg image/jpeg 505087 2PhL4m3s9gZ68vZc/EUSZA== 1536 2048 2022-04-05T15:23:17.000Z https://drive.google.com/uc?
require 'net/http'
require 'net/https'
require 'json'
require 'uri'
class ActiveStorage::Service::PinataService < ActiveStorage::Service
BASE_URL = "https://uploads.pinata.cloud"
class_attribute :files, default: {}, instance_writer: false
{
"plus_code" :
{
"compound_code" : "PVFG+4W8 Lisbon, Portugal",
"global_code" : "8CCGPVFG+4W8"
},
"results" :
[
{
"address_components" :