Embracing Change: The API Evolution

Aleksandra Sikora, @aleksandrasays

whoami

- open-source engineer @
- πŸ§—β€β™‚οΈ
- org of WrocΕ‚aw

previously
-         .js Maintainer
-  cc           

🐦 @aleksandrasays
πŸ™ @beerose
🐘 @aleksandra@mas.to
🌎 https://aleksandra.codes

Software β‰ˆ Fashion

🀯

your work project VS your side project

Paris Fashion Week Feb '23

Elsa Hosk, 2023, Cannes

πŸ‘— πŸ’» πŸ“ˆ

πŸ—Ί

What is an API?

Why are we talking about APIs?

Server & Client

Server & Client

πŸ₯΄

Server & Client

😭

API Layer Problems

Boilerplate

Lost typesafety

Repetitive error handling

πŸ€”

API Layer Problems

Fullstack TypeScript app

TYPE-SAFE

TYPE-SAFE

RPC

1981

What is RPC?

// one-computer.js

function welcome(name) {
  return `Hello, ${name}!`
}

const greeting = welcome("Helsinki!")
//    ^ "Hello, Helsinki!"
// server.js

function welcome(name) {
  return `Hello, ${name}!`
}

startImaginaryServer({ welcome })
// client.js

const greeting = await fetch(
  `https://aleksandra.says/rpc/welcome`,
  { body: JSON.stringify("Helsinki") }
)

What is RPC?

calling remote procedures as if they were local

😌

πŸ₯΄

  • Client & server tightly coupled
  • Having to use the same language
  • Need to learn all the procedure names
  • Having to use multi-threaded servers
  • Parameters marshalling
  • Exception handling

Problems with RPC

RPC -> non-agnostic

RPC -> non-agnostic

β€”β€”β€”β€”β€”

now we're looking for sth

object oriented programming

 

CORBA

1991

NOT Γ‡ORBA

AND NOT COBRA

module Finance {
  typedef sequence<string> StringSeq;
  struct AccountDetails {
    string     name;
    StringSeq  address;
    long       account_number;
    double     current_balance;
  };
  exception insufficientFunds { };
  interface Account {
    void deposit(in double amount);
    void withdraw(in double amount)
                        raises(insufficientFunds);
    readonly attribute AccountDetails details;
  };
};

IDL

Developer trying to learn Corba

  • Complexity
  • Steep learning curve
  • Mapping problems
  • Name confused with a poisonous snake

Problems with CORBA

CORBA -> complex

simple

-                     -

CORBA -> complex

now we need

SOAP

1998

<?xml version="1.0"?>
<soap:Envelope
xmlns:soap="http://www.w3.org/2003/05/soap-envelope/"
soap:encodingStyle="http://www.w3.org/2003/05/soap-encoding">
  <soap:Body>
    <m:GetUserResponse>
      <m:Username>Tony Stark</m:Username>
    </m:GetUserResponse>
  </soap:Body>
</soap:Envelope>
<?xml version="1.0"?>
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
  <soap:Header>
  </soap:Header>
  <soap:Body>
    <m:GetUser>
      <m:UserId>123</m:UserId>
    </m:GetUser>
  </soap:Body>
</soap:Envelope>

πŸ” ✈️

  • Heavy, requires more bandwidth
  • POST = no cache on HTTP layer
  • Tightly coupled with server
  • Inflexible

Problems with SOAP

SOAP -> heavy

light

-                   

SOAP -> heavy

now we're looking for sth

REST

2000

When the web started to change

Can request and update resources

Exposes resources

Operation RPC REST
Login POST /login POST /sessions
Logout POST /logout DELETE /sessions
Get user by id GET /getUser?id=123 GET /users/123
Get user's todo items GET /getTodos?userId=123 GET /users/123/todos
Add new todo item POST /addTodo POST users/123/todos
Update todo item POST /updateTodo PUT /todos/1 
Delete todo item POST /deteteTodo DELETE /todos/1

RPC vs. REST

JSON-RPC

RESTful

"REST"

  • Over fetching
  • Big payloads
  • n+1 problem
  • Limiting constraints
  • No end-to-end typesafety

Problems with REST

REST -> inflexible

-             

REST -> inflexible

time for something

GraphQL

2012

REST API

GraphQL API

API

App

GET users/
GET tasks/
GET tags/

API

App

POST graphql/
Body:
{ "query": "query { users {...} }" }

vs

Client controls the data it gets

User 1

Task 1

Task 2

Tag 1

Tag 2

query {
  user(id: 1) {
    name
    tasks {
      name
      status
      tags {
       id
      }
    }
  }
}

name

surname

age

status

name

priority

name

priority

status

description

id

Tag 3

id

description

id

description

id

  • Same POST-caching problem as in SOAP
  • You have to generate types
  • If you use tools like Hasura,
    you push a lot of domain logic to frontend
  • Otherwise β€” boilerplate!

Problems with GraphQL

at least until stuff like Max Stoiber's GraphQL CDN popped up

GraphQL -> extra work & type-safety

-                           

GraphQL -> extra work & type-safety

 -

-                           

for free and out of the box

Yves Saint Laurent

1968

2020

RPC

2020

Revisiting the original promise of RPC

1981

?

🦠

Source: https://www.vice.com/en/article/m7azaa/best-baggy-oversized-clothes

πŸ‘€

Fullstack TypeScript app

Fullstack TypeScript app

Fullstack TypeScript app

tRPC query & mutation procedures

Remix loader pattern

React Server Components

Qwik City

Blitz RPC query & mutation resolvers

pRPC

Blitz RPC

Blitz RPC

// src/queries/getProject.ts (runs on the server)
import db from "db"
import * as z from "zod"

const GetProject = z.object({
  id: z.number(),
})

export default async function getProject(
  input: z.infer<typeof GetProject>
) {
  // Validate the input
  const data = GetProject.parse(input)

  const project = await db.project.findOne({ where: { id: data.id } })

  // Can do any processing, fetching from other APIs, etc

  return project
}

Blitz RPC

// src/pages/index.tsx (runs on the client & server)
import { useQuery } from "@blitzjs/rpc"
import getProject from "src/projects/queries/getProject"

function Index() {
  const [project] = useQuery(getProject, { where: { id: 1 } })
  
  return <span>{project.id}</span>
}

Blitz RPC

// src/pages/index.tsx (runs on the client & server)
import { useQuery } from "@blitzjs/rpc"
import getProject from "src/projects/queries/getProject"

function App() {
  const [project] = useQuery(getProject, { where: { id: 1 } })
  
  return <span>{project.id}</span>
}

πŸ€”

tRPC

tRPC

export const trpc = createTRPCNext<AppRouter>({ /* ... */});

// ---
function ProjectForm(props) {
  const updateProjectMutation = trpc.createProject.useMutation()

  return (
    <form
      onSubmit={values => {
        try {
          const project = mutation.mutate(values)
        } catch (error) {
          alert('Error saving project')
        }
      }}>
      {/* ... */}
    </form>
  )
}

RPC in the same file as in:

- @builder.io/qwik-city

- @tanstack/bling

server$

server$

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

export default function Index() {
  const { isLoading, error, data } = useQuery({
    queryKey: ['project'],
    queryFn: server$(async () => { // 🀯
      return await db.project.findOne({ where: { id: input.id } });
    })
  })
    
  if (isLoading) return <span>Loading...</span>;
  if (error) throw error;
    
  return <span>{data.id}</span>;
}

React Server Components

// components/Counter.tsx 
'use client'; // πŸ‘€
import { useState } from 'react';

export function Counter() {
  const [state, setState] = useState(0)
  return <button onClick={() => setState(s => s + 1)}>increment: {state}</button>
}

React Server Components

// app/page.tsx
import { db } from '../db';
import { Counter } from '../components/Counter.tsx';

export default function Page() {
  const posts = await db.posts.select('name', 'slug');

  return (
    <div>
      <Counter />
      <ul>
        {posts.map(post => (
          <li key={post.slug}>
            <a href={`/blog/${post.slug}`}>{post.name}</a>
          </li>
        ))}
      </ul>
    </div>
  )
}

Source: https://twitter.com/markdalgleish/status/1256800146118959109

ChloΓ©, Spring 2023

2001

again, non-agnostic

once again, we'd like something agnostic

type-safety is a must

FETS

😌 Type-safety out of the box

πŸš€ No runtime overhead

πŸ”Ž IDE features

Summary

?

Source: http://www.ashleyedavidson.com/blog/the-history-of-fashion-diffusion-in-pictures

Know your options

Take what fits you

Tailor to your needs

Thank you!

@aleksandrasays

www.aleksandra.codes

Future Frontend: API Evolution

By Aleksandra Sikora

Future Frontend: API Evolution

  • 1,783