Placegoose

Take a gander!

// Run this code in a console, or from any app:
fetch("https://placegoose.mies.workers.dev/geese/1")
    .then((response) => response.json())
    .then((json) => console.log(json));

What's it good for?

Placegoose is a free online REST API for moments when you just need some honkin data! More than that, it's a learning resource for building blazing-fast REST APIs with the HO(N)C stack. You're welcome to use it as a reference, or clone the repo and modify the schema to fit your use-case.

How does the API work?

Placegoose comes with 3 goose-themed resources, supporting up to all 5 common HTTP verbs. We don't actually make updates to the DB in response to write requests, but we do validate payloads and verify the target exists. If something's not right, we'll return an error with a helpful message.

To learn more about making requests, or what different errors mean, check out our guide!

Resources

/gaggles 10 gaggles
/geese 100 geese, each part of a gaggle
/honks 500 honks, each made by a goose

Flying solo

You might want to build your own (mock?) data service, or may just be curious about the stack. This project was built using Hono and Drizzle ORM to highlight core features and implementation patterns. For more examples, or to get going with a template, check out our repo.

Take a deep dive into the development process and key features of the stack in our blog series. You'll need a basic understanding of TypeScript REST APIs and Cloudflare, but we tried to cover everything you'll need to get started!

Making requests

Reference

Get All Create
Get All (By Relation) Modify
Get By Id Delete
Update Errors

Get All Records

Filtering, sorting, and pagination are not currently supported.

fetch("https://placegoose.mies.workers.dev/honks")
    .then((response) => response.json())
    .then((json) => console.log(json))
    .catch((error) => console.error(error));

Status: 200

[
    {
        "id": 1,
        "gooseId": 16,
        "decibels": 104
    },
    // ...
    {
        "id": 100,
        "gooseId": 17,
        "decibels": 82
    },
]

Get All Records (Filtered by relation)

This feature is only available for the /honks resource at this time.

fetch("https://placegoose.mies.workers.dev/honks?gooseId=1")
    .then((response) => response.json())
    .then((json) => console.log(json))
    .catch((error) => console.error(error));

Status: 200

[
    {
        "id": 78,
        "gooseId": 17,
        "decibels": 110
    },
    {
        "id": 100,
        "gooseId": 17,
        "decibels": 82
    }
]

Create Record

Geese cannot be created by API request.

fetch("https://placegoose.mies.workers.dev/honks", {
    method: "POST",
    body: JSON.stringify({
        gooseId: 42,
        decibels: 64,
    }),
    headers: {
        "Content-Type": "application/json; charset=UTF-8",
    },
})
    .then((response) => response.json())
    .then((json) => console.log(json))
    .catch((error) => console.error(error));

Status: 201

{
    "id": 325,
    "gooseId": 42,
    "decibels": 64
}

Get Record By ID

fetch("https://placegoose.mies.workers.dev/honks/1")
    .then((response) => response.json())
    .then((json) => console.log(json))
    .catch((error) => console.error(error));

Status: 200

{
    "id": 1,
    "gooseId": 16,
    "decibels": 104
}

Update Record

In most cases, PUTs update the whole record, but honks are read-only properties.

fetch("https://placegoose.mies.workers.dev/honks/1", {
    method: "PUT",
    body: JSON.stringify({
        decibels: 299,
    }),
    headers: {
        "Content-Type": "application/json; charset=UTF-8",
    },
})
    .then((response) => response.json())
    .then((json) => console.log(json))
    .catch((error) => console.error(error));

Status: 200

{
    "id": 1,
    "gooseId": 16,
    "decibels": 299
}

Modify Record

PATCHes accept partial updates.

fetch("https://placegoose.mies.workers.dev/honks/1", {
    method: "PATCH",
    body: JSON.stringify({
        decibels: 36,
    }),
    headers: {
        "Content-Type": "application/json; charset=UTF-8",
    },
})
    .then((response) => response.json())
    .then((json) => console.log(json))
    .catch((error) => console.error(error));

Status: 200

{
    "id": 1,
    "gooseId": 16,
    "decibels": 36
}

Delete Record

DELETes don't return a body.

fetch("https://placegoose.mies.workers.dev/honks", {
    method: "DELETE",
})
    .then((response) => response.json())
    .then((json) => console.log(json))
    .catch((error) => console.error(error));

Status: 204

Errors

Whens something goes wrong, we'll try and share a helpful message! If you're running your own instance, use Fiberplane Studio to inspect logs and the request timeline.

type ErrorData = {
    message: string;
    /** Only included for validation errors */
    issues?: Record<string, string[]>;
}
Status Issue
400 Request Error: Make sure the request is properly formatted, and that you're using a valid ID and payload (if applicable).
403 Forbidden: The darkness beacons.
404 Not Found: Verify you're using an ID within the resource limits or try a Get All request to double-check.
500 Something went terribly wrong. If you're using our deployed service, please create an issue to let us know.

Routes

Gaggles

type Gaggle = {
    id: number;
    name: string;
    territory: string | null;
}
GET /gaggles
POST /gaggles
GET /gaggles/1
GET /gaggles/1/geese
PUT /gaggles/1
DELETE /gaggles/1

Geese

type Goose = {
    id: number;
    gaggleId: number | null;
    name: string;
    isMigratory: boolean;
    mood: "hangry", "waddling", "stoic", "haughty", "alarmed" | null;
}
GET /geese
GET /geese/:id
GET /geese/:id/honks

Honks

type Honk = {
    id: number;
    // Note: honks cannot be reassigned to a different goose
    gooseId: number;
    decibels: number;
}
GET /honks
GET /honks?gooseId=1
POST /honks
GET /honks/1
PATCH /honks/1
PUT /honks/1
DELETE /honks/1