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