Skip to content
Gabriel Sferra

Gabriel Sferra

Full Stack Developer

I build web and mobile products end to end, from the database to the interface.

Marília, SP · Brazil

apps/api/src/db/schema.tsOrbital
// O banco recusa vincular um gasto a uma categoria de receita. A coerência
// deixa de depender de lembrarmos de validar em cada rota.
foreignKey({
  columns: [t.categoryId, t.kind],
  foreignColumns: [categories.id, categories.kind],
  name: 'transactions_category_kind_fk',
}),
netlify/functions/state.mjs(private repository)Grit
const SAVE_STATE = `insert into states (user_id, doc) values ($1, $2::jsonb)
  on conflict (user_id) do update set doc = excluded.doc, saved_at = now()`;
/* … */
const SAVE_STATE_IF_NOT_OLDER = `${SAVE_STATE} where states.updated_at <= excluded.updated_at returning user_id`;
backend/app/Services/RegistrationService.phpFull Stack Sign-up
if (preg_match('/^(\d)\1{10}$/', $cpf)) {
    return false;
}

$sum = 0;
for ($i = 0; $i < 9; $i++) {
    $sum += intval($cpf[$i]) * (10 - $i);
}
$remainder = $sum % 11;
$digit1 = ($remainder < 2) ? 0 : 11 - $remainder;

Projects

What I have built, with the code behind each decision.

In development

Orbital

Personal finance app for iPhone, Android and the web, served by a single API.

The problem
Tracking expenses in a spreadsheet does not follow you on your phone. Orbital brings budgeting to iPhone, Android and the web without duplicating business rules: all three talk to the same API.
What I did
Personal project: database design, API, cross-platform app and tests.

Technical decisions

  • Monorepo with the API contract in shared Zod schemas: if a response shape changes, the app fails to type-check immediately.
  • Money is always stored in cents (bigint) and entries as calendar dates, avoiding floating-point and timezone bugs.
  • Composite foreign key: the database itself rejects an expense linked to an income category.
  • Recurring entries are generated on read, with no cron job, and a unique index prevents duplicates.

82 tests covering pt-BR amount parsing, month and timezone boundaries, and recurrence. In development, invite-only sign-up.

  • TypeScript
  • Expo
  • React Native
  • Hono
  • Drizzle
  • Better Auth
  • Zod
  • Cloudflare Workers
  • PostgreSQL
apps/app/lib/api.tsOrbital
// Só o TIPO cruza a fronteira: o TypeScript enxerga cada rota e cada resposta,
// mas o Metro não empacota uma linha do servidor dentro do app.
import type { AppType } from '@financas/api'
/* … */
const rpc = hc<AppType>(API_URL, {
  /* … */
  1. Line 3Only the API type enters the app. Not a single line of server code ships to iPhone, Android or the web.
  2. Line 5The client knows every route and response. If the API changes a shape, the app stops compiling right away.
Live

Grit

Diet and training app for iPhone with a daily checklist that works offline.

The problem
Following a diet and a training plan means logging meals, loads and water throughout the day. Grit does that on the iPhone, works offline and syncs when a connection is available.
What I did
Personal project in production: UI, database, API, notifications and tests. Used by me and by friends, each with their own account.

Technical decisions

  • Offline-first: data lives on the device and syncs with Postgres; conflicts are settled in a single conditional write, with no race conditions.
  • Hand-written SQL and versioned migrations, no ORM.
  • Push notifications on the iPhone through standard Web Push, with no native app or Apple developer account.
  • Diet plans proposed by an AI agent only reach the user after passing the app’s own calorie and macro rules.

Live and in daily use, with 274 automated tests, including the API against a real Postgres in Docker.

  • JavaScript
  • Vite
  • Tailwind CSS
  • PWA
  • Netlify Functions
  • PostgreSQL (Neon)
  • Web Push
  • node:test
Grit workout screen on iPhone: Workout A, chest and triceps, with the exercise list
src/lib/diet-plan.js(private repository)Grit
export function checkProposal(proposal, { targets, customFoods = [] }) {
  /* … */
  if (errors.length) return refused(errors);
  const totals = roundMacros(sumMacros(planMeals.flatMap(meal => meal.foods))), items = targetItems(totals, targets);
  /* … */
  return { errors, plan, items, ok: items.every(item => item.ok) };
}
  1. Line 3A malformed proposal is rejected before any math runs.
  2. Line 6It is only approved if calories and every macro fall within the person’s target range.
Open source

Full Stack Sign-up

User registration system with Google OAuth 2.0 sign-in.

The problem
Register and list users with social login, Brazilian data validation and email delivery, without blocking the interface.
What I did
Laravel back end, Vue front end, tests and Docker environment.

Technical decisions

  • Repository Pattern and a service layer, with dependency injection through Service Providers.
  • Automatic token refresh, CPF (Brazilian ID) validation and server-side pagination with filters.
  • Emails sent asynchronously through Redis queues.
  • The whole environment (API, front end, MySQL and Redis) starts with one docker compose.

Unit and API tests with PHPUnit on the back end and Vitest on the front end.

  • Laravel 11
  • PHP
  • Vue 3
  • TypeScript
  • MySQL
  • Redis
  • Docker
backend/app/Services/RegistrationService.phpFull Stack Sign-up
$user = $this->userRepository->update($userId, [
    'name' => $data['name'],
    'birth_date' => $data['birth_date'],
    'cpf' => $data['cpf'],
    'registration_completed' => true,
]);
/* … */
SendRegistrationEmail::dispatch($user, $email);
  1. Line 1The service writes through the repository, not Eloquent directly: persistence can change without touching business rules.
  2. Line 8The email goes to a Redis queue, so the response does not wait for delivery.
Gabriel Sferra wearing a navy suit and tie

About

I am a full stack developer based in Marília, Brazil. I started in front-end, building online stores for clients of Auaha Agency, and today I work at Tray as a Test Analyst, validating features in marketplace integrations.

Working in software testing changed how I build: I model data so the database rejects invalid states, I like types that flow from server to client, and I focus tests where a bug would be costly.

Outside work, I build my own products: a personal finance app that runs on iPhone, Android and the web, and a training and diet PWA that I use in my daily routine with friends.

  • Fluent in English

Technologies

What I use at work and in my own projects, by area.

Front-end

  • TypeScript
  • JavaScript
  • Vue 3
  • React
  • React Native (Expo)
  • Tailwind CSS
  • SCSS

Back-end

  • Node.js (Hono)
  • PHP (Laravel)
  • C# (ASP.NET Core MVC)

Data & infra

  • PostgreSQL
  • MySQL
  • Redis
  • Docker
  • Cloudflare Workers
  • Netlify

Quality

  • PHPUnit
  • Vitest
  • node:test
  • ESLint
  • Manual and exploratory testing

E-commerce

  • TrayCorp
  • Wake
  • MercadoShops

Experience

Work

  1. – present

    Test Analyst

    Tray (opens in a new tab)

    Feature validation for marketplace integrations in the Tray ecosystem, including test planning and automation.

  2. – , 2 years 1 month

    Front-End Developer

    Agência Auaha

    I joined as an intern and stayed on as a front-end developer, building online stores for clients such as Mãe Terra, Spilpack, Defitness, RS Air Filter, ABC da Construção and Suggar, on the TrayCorp, Wake and MercadoShops platforms. Agile team using Scrum.

Education

  1. –

    Postgraduate degree in Full Stack Development

    PUCRS (Pontifícia Universidade Católica do Rio Grande do Sul)

  2. –

    B.Sc. in Computer Science

    Univem (Centro Universitário Eurípides de Marília)