Lorenzo SanseverinoThe Last Developer
IT EN

The Last Developer

Lorenzo Sanseverino

After the AI apocalypse, I'm still here reading the code I ship. Shopify themes, Java backends and software that's thought through, optimized, functional and actually good to look at.

  • Shopify
  • Liquid
  • JavaScript
  • TypeScript
  • Java
  • Spring
  • MongoDB
  • Redis
  • Next.js
  • GraphQL

Skills

The technologies I work with every day.

Shopify

Online Store 2.0 themes, Liquid, sections, metafields and the Storefront API.

{% render 'card', product: product %}

JavaScript

Vanilla and Custom Elements, keeping the browser workload minimal.

customElements.define('x-tabs', Tabs)

TypeScript

Typed React and Next.js interfaces, with contracts shared with the APIs.

type View = Partial<Summoner>

CSS

Scroll-driven animations, :has(), container queries and @property instead of JavaScript.

animation-timeline: view()

Java

Records, streams and concurrency for queues, schedulers and services on Debian VPSes.

record Rank(String tier, int lp) {}

Spring Boot

REST controllers on embedded Tomcat, validation and scheduled jobs.

@GetMapping("/profile/{puuid}")

MongoDB

Canonical data, aggregation pipelines and facets for statistics.

db.match.aggregate(pipeline)

MySQL

Relational schemas and hand-written queries. MariaDB in production for guilds and settings.

SELECT prefix FROM guild WHERE id = ?

Frontend

  • React
  • Next.js
  • GraphQL
  • Astro
  • Vite

Backend and data

  • Redis
  • MariaDB
  • JDA
  • Maven

Infra and tools

  • Git
  • GitHub
  • Debian
  • Cloudflare
  • Algolia

Projects

Beebot and Iodid

A Java backend and a Next.js frontend forming a single League of Legends statistics platform. Pick a scenario and see what happens behind each request.

Backend · Java

Beebot

Discord bot and League of Legends statistics engine, written in Java. The APIs never wait for Riot: they read from Redis and Mongo and queue the rest.

  • Cache-aside: derived Redis, Mongo as the source
  • Sliding-window rate limiter towards Riot
  • PENDING, PARTIAL and READY states for the client
STACK
Java · Spring · JDA · MongoDB · Redis
SCOPE
Bot, tracker, job queue, API
STATUS
Active, service split in progress
Open case study
beebot / api trace
Scenario
  1. API
  2. Redis
  3. MongoDB
  4. Riot
  1. GET /api/lol/EUW1/profile/{puuid}
  2. REDIS GET beebot:lol:ls:EUROPE:EUW1:{puuid}:summoner HIT · 2 ms
  3. 200 SummonerView 6 ms
  1. API
  2. Redis
  3. MongoDB
  4. Riot
  1. GET /api/lol/EUW1/profile/{puuid}
  2. REDIS GET beebot:lol:ls:EUROPE:EUW1:{puuid}:summoner MISS · 1 ms
  3. MONGO summoner.findOne({ puuid }) HIT · 9 ms
  4. REDIS SET beebot:lol:ls:EUROPE:EUW1:{puuid}:summoner 1 ms
  5. 200 SummonerView 15 ms
  1. API
  2. Redis
  3. MongoDB
  4. Riot
  1. GET /api/lol/EUW1/profile/{puuid}
  2. REDIS GET beebot:lol:ls:EUROPE:EUW1:{puuid}:summoner MISS · 1 ms
  3. MONGO summoner.findOne({ puuid }) MISS · 8 ms
  4. QUEUE QueueHandler.immediate(...) → RiotScheduler QUEUED
  5. 202 profile_pending 11 ms
  6. RIOT GET /riot/account/v1/accounts/by-puuid/{puuid} 200 · 141 ms
  7. RIOT GET /lol/summoner/v4/summoners/by-puuid/{puuid} 200 · 152 ms
  8. MONGO summoner.insertOne(profile) 7 ms
  9. REDIS SET beebot:lol:ls:EUROPE:EUW1:{puuid}:summoner 1 ms
  10. 200 SummonerView RETRY · 6 ms
  1. API
  2. Redis
  3. MongoDB
  4. Riot
  1. GET /api/lol/EUW1/profile-by-name/Player/EUW
  2. REDIS Riot ID lookup · Player#EUW · EUW1 MISS · 1 ms
  3. MONGO saved summoner lookup by Riot ID MISS · 8 ms
  4. RIOT GET /riot/account/v1/accounts/by-riot-id/Player/EUW 404 · 138 ms
  5. 404 not_found 149 ms

Example with indicative timings. Riot calls always go through the queue.

Frontend · TypeScript

Iodid

League of Legends statistics inside a virtual operating system: every profile is a folder, every page a window, every path a URL.

  • Virtual filesystem with ls, cd, tree and profile
  • Window manager and terminal on top of Next.js
  • VFS paths indexable as pages
STACK
Next.js · React · TypeScript
SCOPE
Profiles, champions, builds, leaderboards
STATUS
In development
Open case study
iodid / terminal
Scenario
  1. Shell
  2. VFS
  3. Beebot API
  4. Window
  1. iodid:~ $ cd /
  2. iodid:/ $ ls
  3. champions/ summoners/ match/ leaderboard/ tier-list/ records/ arena/ htop/ home/
  4. iodid:/ $ cd summoners/Chiaretta-0117
  5. iodid:/summoners/Chiaretta-0117 $ ls
  6. champions/ activity/ matchups/ lp-gains/ matches/ records/ livegame/
  1. Shell
  2. VFS
  3. Beebot API
  4. Window
  1. iodid:/summoners/Chiaretta-0117 $ profile
  2. → GET /api/lol/EUW1/profile-by-name/Chiaretta/0117
  3. ← 200 SummonerView 148 ms
  4. ▣ terminal viewer · Chiaretta#0117
  5. ↗ url /euw1/profile/Chiaretta-0117
  1. Shell
  2. VFS
  3. Beebot API
  4. Window
  1. iodid:/summoners/Chiaretta-0117 $ tree -L 1
  2. ├── champions/ ├── activity/ ├── matchups/
  3. ├── lp-gains/ ├── matches/ ├── records/
  4. └── livegame/
  5. iodid:/summoners/Chiaretta-0117 $ cd lp-gains
  6. → GET /api/lol/EUW1/profile/{puuid}/rank-history
  7. ← 200 Solo/Duo · Flex · Premade 96 ms
  8. ▣ terminal viewer · lp-gains

Every command resolves a VFS node; the data comes from the Beebot APIs.

  • Pomellato
  • Slam Jam
  • Rick Owens
  • Antonioli
  • Montura
  • Kappa

Shopify

More CSS, less JavaScript.

Theme, component and integration development for fashion and luxury brands.

Cascading variant picker

CSS · Liquid

Filters built with :has() and attribute selectors. Liquid precomputes variantMap and sold-out states, JS only handles resets.

Linked product swatches

Liquid · Algolia

For Kappa: JSON metafields, hard-stop gradient swatches and hidden radios.

Automated variant metafields

GraphQL

For Montura: bulk updates of seasonal fields across variants.

OneTrust and Shopify consent

JS · Privacy

OneTrust wired into Shopify’s Customer Privacy API.

How the game works: CSS only

Speed
  1. The level is a grid

    Every obstacle has a column: left = x × cell. No canvas, no coordinates in JS.

  2. The world moves, the cube does not

    One keyframe translates the course by one tile in a loop; the cube stays still on the X axis.

  3. The jump is a curve

    The jump keyframes land on the obstacle columns, with cubic-bezier easing for gravity.

  4. JavaScript is only there to explode

    A click sets a data attribute: particles, shockwave and restart are CSS.

[data-obstacle] {
  left: calc(var(--x) * var(--cell));
}
[data-world] {
  animation: world var(--period) linear infinite;
}
@keyframes hop {
  22%, 41% { translate: 0 0 }
  31% { translate: 0 calc(-2.2 * var(--cell)) }
}
[data-lab]:has(#lab-grid:checked) [data-x]::after {
  content: "x " attr(data-x);
}

Philosophy

The philosophy of code.

Code deserves thought, optimization and a little taste. It has to work and be good to read. Even after the AI apocalypse.

  1. AI is a tool

    It is now part of the job and makes it faster. Control stays human, though: every line has to be understood, verified and chosen.

  2. Ownership

    Whoever writes the code answers for it. Every piece of logic has one owner and every decision has a reason.

  3. Cleanliness

    Explicit names, clear responsibilities, nothing superfluous. Clean code costs less with every change.

  4. Few lines, easy to follow

    Brevity only has value if it stays readable. Less code means fewer bugs and less to maintain.

naming/no-type-in-name JAVA

The type is already the type

The declaration already says int: repeating it in the name is noise. A local integer is count, not itemCounterInt.

int itemCounterInt = items.size();int count = items.size();
naming/explicit-unit JAVA

The unit goes in the name

A long can hold seconds, milliseconds or nanoseconds: here the name has to say which. Better still, a type that already knows.

long start = System.currentTimeMillis();long timeout = 30;long startTimeMillis = System.currentTimeMillis();Duration timeout = Duration.ofSeconds(30);
naming/no-abbreviations JS

No abbreviations

Code is written once and read a hundred times. Three saved letters are not worth a single doubt.

const usrCfg = getCfg(req);const btn = el.querySelector('.btn');const userConfig = getConfig(request);const button = element.querySelector('[data-button]');
comments/name-over-comment JS

A name instead of a comment

If a condition needs a comment, it deserves a named constant.

// check if the user is an adminif (user.role === 'admin' && user.active) {const isAdmin = user.role === 'admin' && user.active;if (isAdmin) {
flow/early-return JAVA

Early return, no nesting

Rule out the cases that do not matter first, then the main path stays flat.

if (player != null) {    if (player.isRanked()) {        return player.getRank();    }}return Rank.UNRANKED;if (player == null || !player.isRanked()) return Rank.UNRANKED;return player.getRank();
size/readable-lines JS

Few lines, but readable

The short version only wins if it reads better than the long one.

let r = false;for (let i = 0; i < p.v.length; i++) {  if (p.v[i].a) r = true;}return r;return product.variants.some((variant) => variant.available);

Developers first. Then the product. Then the clients.

It's a joke. Mostly. Looking after the people writing the code is the first step to looking after the product and the people using it.

Gaming

When the editor closes, the client opens.

My League of Legends stats, served by the Beebot API, plus a few clips. More games coming soon.

Season 2026 · Support main

Win rate47.7%21 W · 23 L
Average KDA2.16last 20 games
Form5W · 5Llast 10

Most played champions

Solo/Duo
Thresh 14 games50.0%
Brand 5 games60.0%
Galio 3 games33.3%
Singed 3 games66.7%
Nautilus 3 games100.0%

Recent games

  1. Defeat
    GalioArena · 23:26
    2/6/163.00 KDA
    — 08/11
  2. Victory
    AmumuOther · 22:52
    1/3/72.67 KDA
    — 08/11
  3. Victory
    KaynOther · 21:03
    3/1/47.00 KDA
    — 08/11
  4. Defeat
    NasusOther · 22:04
    5/5/62.20 KDA
    — 08/11

Contact

Let’s talk about your project.