APIs and Application State
Learning objectives
- You know how to interact jointly with state and APIs.
APIs can also be linked with application state so that whenever the application state changes, the API is updated. This is useful when we want to keep the API up-to-date with the application state. For example, if we have a list of items in the application state, we can create an API that returns the list of items. Whenever the list of items changes, the API is updated to reflect the changes.
The following example outlines a possible API for the earlier count example, where we keep track of a count and can increase the count. It is a Deno application that leverages Hono to keep track of the count. Note also the use of the cors
middleware to allow cross-origin requests.
import { Hono } from "https://deno.land/x/hono@v3.12.11/mod.ts";
import { cors } from "https://deno.land/x/hono@v3.12.11/middleware.ts";
const app = new Hono();
app.use('/*', cors())
let count = 0;
app.post("/count", (c) => {
count++;
return c.json({ count });
});
app.get("/count", (c) => c.json({ count }));
Deno.serve(app.fetch);
Now, to use such an API, we would wish to create a separate file that contains the API calls. If the project that you're working on does not yet contain a folder http-actions
under the src
folder, create the http-actions
folder. Then, create a file count-api.js
in the http-actions
folder.
The following example outlines two functions; one for retrieving the count from the API, and one for updating the count with the API. Note that for now, the API is assumed to be running on http://localhost:8000
-- we'll later look into ways for injecting the API address to the application.
const getCount = async () => {
const res = await fetch("http://localhost:8000/count");
const data = await res.json();
return data.count;
};
const incrementCount = async () => {
await fetch("http://localhost:8000/count", { method: "POST" });
};
export { getCount, incrementCount };
Now, we can modify our earlier counter.svelte.js
to use the API instead of localStorage (both could be used though). Now, instead of retrieving the initial value from the local storage, we retrieve the initial value from the API. As you might notice, this is done using an asynchonous function call that is defined after setting the initial state. This is because we cannot use await
in the top-level of a JavaScript file (even though this is OK in Deno). In addition to retrieving the count, we also call the incrementCount
function whenever the count is incremented.
import * as countApi from "../http-actions/count-api.js";
let count = $state(0);
const initCount = async () => {
count = await countApi.getCount();
}
initCount();
const useCountStore = () => {
return {
get count() {
return count;
},
increment: () => {
count++;
countApi.incrementCount();
},
};
};
export { useCountStore };
Now, the components that would use the above store would not need to change. For example, the Counter.svelte
component would remain the same.
Svelte, Docker, and Server-side rendering
By default, the up-to-date Svelte version does server-side rendering in. This leads to the above example not working out of the box in Docker, as the concept of localhost
is missing in Docker. To get around this, either create a +page.js
file to the routes
folder in the src
and add the following line to the file:
export const ssr = false;
Or, add error handling to the getCount
function as follows.
const getCount = async () => {
let res;
try {
res = await fetch("http://localhost:8000/count");
} catch (e) {
return 0;
}
const data = await res.json();
return data.count;
};