Take a Rest from REST

โ€” Aleksandra Sikora, @aleksandrasays

What's this talk about?

What is an API?

Why are we talking about APIs?

๐Ÿ‘ฉโ€๐Ÿ’ป

API Layer Problems

๐Ÿ˜ญ

API Layer Problems

Boilerplate

Lost typesafety

Repetitive error handling

= additional complexity

๐Ÿค”

API Layer Problems

Boilerplate

Lost typesafety

Repetitive error handling

API Layer Problems

๐Ÿ’โ€โ™€๏ธ

tRPC: query & mutation procedures

Remix: loader pattern

Blitz RPC: query & mutation resolvers

React: Server-side components

API Layer Problems

๐Ÿ’โ€โ™€๏ธ

tRPC: query & mutation procedures

Remix: loader pattern

Blitz RPC: query & mutation resolvers

React: Server-side components

RPC

1981

What is RPC?

// one-computer.js

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

const greeting = welcome("React Berlin")
//    ^ "Hello, React Berlin!"

What is RPC?

// server.js

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

startImaginaryServer({ welcome })
// client.js

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

What is RPC?

// server.js

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

startImaginaryServer({ welcome })
// client.js

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

RPC is the practice of remotely calling functions

๐Ÿ˜Œ

๐Ÿฅด

  • No request cancellation
  • Having to use multi-threaded servers
  • Parameters marshalling
  • Exception handling

Problems with RPC

Solution ๐Ÿ‘‰

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

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

Problems with CORBA

Solution ๐Ÿ‘‰

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

Problems with SOAP

Solution ๐Ÿ‘‰

REST

2000

Can request and update resources

Exposes resources

ย 

  1. Uniform interface
  2. Clientโ€“server architecture
  3. Stateless
  4. Cacheable
  5. Layered system
  6. Code on demand (optional)

REST APIs

SOAP vs. REST

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

RPC over HTTP using JSON

RESTful

"REST"

  • Under and over fetching
  • Big payloads
  • n+1 problem
  • Limiting constraints
  • No end-to-end typesafety

Problems with REST

Solution ๐Ÿ‘‰

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

What's nextโ“

  • 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

RPC

1981ย 2022

Revisiting the original promise of RPC

ใ€ž

As TypeScript and static typing increasingly becomes a best practice in web development, API contracts present a major pain point. We need better ways to statically typeย our API endpoints and share those typesย between our client and server (or server-to-server).

โ€“ tRPC docs

// server.ts

import db from "prisma"

async function welcome(userId: string) {
  const user = await db.users.findFirst({ where: { id: userId }})
  
  return `Welcome again, ${user.name}!`
}

startImaginaryServer({ welcome })
// client.ts

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

Type safe backend

Unsafe frontend

// server.js

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

startImaginaryServer({ welcome })

โŒ

Type safe backend

Unsafe frontend

// server.ts

import db from "prisma"

async function welcome(userId: string) {
  const user = await db.users.findFirst({ where: { id: userId }})
  
  return `Welcome again, ${user.name}!`
}

startImaginaryServer({ welcome })
// client.ts

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

import type { Server } from './server.ts'

const rpc = setupImaginaryClient<Server>()

const greeting = await rpc.welcome({ input: 1 })
// server.ts

import db from "prisma"

async function welcome(userId: string) {
  const user = await db.users.findFirst({ where: { id: userId }})
    
  return `Welcome again, ${user.name}!`
}

const server = startImaginaryServer({ welcome })
export type Server = typeof server

DEMO

Takeaway

Cycles in technology

  • We keep revisiting ideas from decades past and improving on them
  • Sometimes what you need today isn't the new, but a spin-off of something oldย 

ใ€ž

RPC is a natural fit for the increasingly ubiquitous practice of full-stack development with frameworks such as Next.js.

โ€“ Telefunc docs

Thank You!

@aleksandrasays

www.aleksandra.codes

www.everybody.gives