Learn soFetch by example.

Basic Requests

A call to soFetch with a single string argument is a GET, so to get an array of type Product from the endpoint "/products" is just:

const products = await soFetch<Product[]>("/products")

POST requests

A call to soFetch with a second argument is a POST request. If you wanted to upload a User to the endpoint "/users" and expect aNewUserResponse back, you could do:

const newUser = {
    name:"Regina George", 
    email:"regina@massive-deal.com"
}

const success = await soFetch<Success>("/api/users", newUser)

What about PUT, PATCH and DELETE requests?

These are all supported:

//A PUT request:
const upsertUser = {
    name:"Regina George",
    email:"regina@massive-deal.com"
}
const successResponse = await soFetch.put<Success>("/api/users/1234", upsertUser)
//A PATCH request:
const updateUserEmail = {
    email:"regina@massive-deal.com"
}
const successResponse = await soFetch.patch<Success>("/api/users/1234", updateUserEmail)
//A DELETE request:
await soFetch.delete("/api/users/1234")

Explicit GET and POST

If you prefer to explicitly label your GET and POST requests they follow the same pattern as PUT, PATCH and DELETE above:

// soFetch<Product[]>("/products") is equivalent to:
const products = await soFetch.get<Product[]>("/products")
// soFetch<Success>("/api/users", someObject) is equivalent to:
const success = await soFetch.post<Success>("/api/users", someObject)

Error Handling

soFetch is await-able so you can catch all asynchronous just like you would with a regular promise:

const result = await soFetch("/api/unicorns/1234").catch((e:any) => {
    console.error(e)
    alert("An unexpected error occurred")
})

Catching specific HTTP errors

However, you'll likely come across situations where you want to handle a specific error. A common scenario would be to handle a 404 - NOT FOUND response from the server:

const result = await soFetch("/api/unicorns/1234").catchHttp(404, (r:Response) => {
    alert("This unicorn could not be found.")
})

Handling multiple error types (handler-chaining)

If you want to handle specific errors in different ways you can chain your error handlers together, like this:

const result = await soFetch("/api/unicorns/1234")
.catchHttp(404, (r:Response) => {
    alert("This unicorn could not be found.")
})
.catchHttp(419, (r:Response) => {
    alert("This unicorn has expired.")
})
.catch((e:any) => {
    //This code will execute if the HTTP response code is not 404 or 419:
    alert("An unexpected error occurred")
})

Note that if you want to catch all errors that weren't explicitly caught by a catchHttp() block you'll need to place the catch() method after the catchHttp()s

Global error handling

To use the same handler every time soFetch receives a specific error response code you can add a handler to the soFetch.config

soFetch.config
.catchHttp(401, (res:Response) => {
    window.location.href = "/login";
})
.catchHttp(500, (res:Response) => {
    alert("An unexpected error has occurred")
})

Reading the server response.

Sometimes a server returns more information than just an error response code and there might be situations where you want to inspect the response body. How can you do this with soFetch? Helpfully, the response argument returned to a catchHttp() error handler is a regular Fetch API Response object, so you to get the response body you could do something like:

const cat = {
    whiskers:44
    attitude:"Aloof"
}
await soFetch.post("/api/dogs", cat).catch(422, (r:Response) => {
    const responseBody = await r.json()
    if (responseBody == "Not a dog") {
        alert("Please post dogs only")
    } else {
        throw new Error("Payload could not be processed")
    }
}

Authentication

soFetch has methods to help you authenticate using modern standards. Use the use...Authentication() config methods to configure authentication before you first call soFetch(). Optionally, you can use config.setAuthToken() to set an authentication token later, typically after a user login or token refresh.

1 Basic Authentication

Uses a Base64-encoded username:password string in the Authorization header.

soFetch.config.useBasicAuthentication({
    username:"Chris Hodges", 
    password:"Antoinette"
})

The example above will add the following header to all subsequent requests:

Authorization:Basic Q2hyaXMgSG9kZ2VzOkFudG9pbmV0dGU

Where Q2hyaXMgSG9kZ2VzOkFudG9pbmV0dGU is a base-64 encoded concatenation of the username and password, separated by a colon.

Form more info on the Basic Authorization scheme check out the Mozilla Docs

2 Bearer Authentication

Bearer Authentication is the most common authentication scheme for websites where uses have to log in. It's the method used by OAuth 2.0 protected resources. Most modern APIs use bearer tokens.

//First tell soFetch to use Bearer Authentication:
soFetch.config.useBearerAuthentication()

//Then, once the user has logged in and received a token you can pass the token to soFetch like this:
soFetch.config.setAuthToken("SOME_ACCESS_TOKEN")

The above example will add the following header to each request but only after the setAuthToken() method has been called.

Authorization:Bearer SOME_ACCESS_TOKEN

By default the bearer token will be persisted in Local Storage on the browser under the key SOFETCH_AUTHENTICATION. In Node the default behaviour is to hold the key in memory for subsequent requests but not persist the token beyond the lifetime of the soFetch instance.

See below for persistence options and how to change the name of the key

3 Setting an Authentication Token in a Custom Header

Some APIs require an API key in a custom header.

soFetch.config.useHeaderAuthentication({
    authToken:"HEADER_ACCESS_TOKEN",
    headerKey:"custom-header-key"
})

The above example will add the header custom-header-key:HEADER_ACCESS_TOKEN to all subsequent requests. By default the token will be persisted to local storage.

4 Adding an Authentication Token to the Query String

This method is not as secure, but still common.

soFetch.config.useQueryStringAuthentication({
    authToken:"ACCESS_TOKEN",
    queryStringKey:"API-KEY",
})

The above example will append a query string parameter with key API-KEY and value ACCESS_TOKEN to each subsequent request, e.g:

https://api.example.com/data?API-KEY=ACCESS_TOKEN

5 Cookie-Based Authentication

Use this method if the server returns authentication cookies after you login.

soFetch.config.useCookieAuthentication()

You can also set a cookie programmatically, but this will only be sent to the server if the currently loaded page and the API are served from the same domain.

//Sets a cookie with key COOKIES_AUTH_KEY and value COOKIES_AUTH_TOKEN:
soFetch.config.useCookieAuthentication({
    authenticationKey:"COOKIES_AUTH_KEY"
})
soFetch.config.setAuthToken("COOKIES_AUTH_TOKEN")

These 5 methods cover the vast majority of real-world cases, but if your case doesn't quite fit, you could also use soFetch hooks.


Hooks

Hooks (a.k.a. event handlers or interceptors) allow you to read and modify requests and responses as they are sent to and received from a server.

beforeSend()

Lets you read and optionally modify a SoFetchRequest before it's sent to the server. If you return a SoFetchRequest from the handler, this modified request will be passed to the next handler and subsequently to the server.

You can add beforeSend() handlers per request...

const resource = await soFetch("/some-resource").beforeSend((req:SoFetchRequest) => {
    req.headers["test-header"] = "Some Value"
    return req //Remember to return the request if you want to modify it
})

or on every request via the config:

soFetch.config.beforeSend((req:SoFetchRequest) => {
    req.headers["test-header"] = "Some Value"
    return req
})
const resource = await soFetch("/some-resource")

Both the examples above will add a custom header to the request with key test-header and value Some Value

beforeFetchSend()

If you're modifying a request before it's sent you'll normally just want to modify the URL, body or headers. But there might be occasions where you want access to the Fetch API RequestInit object itself. For that you can use the beforeFetchSend() hook, which fires after the beforeSend() hook.

You can add beforeFetchSend() handlers per request...

const resource = await soFetch("/some-resource").beforeFetchSend((req:RequestInit) => {
    req.cache = 'reload'
    return req //Remember to return the request if you want to modify it
})

or on every request via the config:

soFetch.config.beforeFetchSend((req:RequestInit) => {
    req.cache = 'reload'
    return req
})
const resource = await soFetch("/some-resource")

onRequestComplete()

A call to soFetch() will make a request and serialise the response all in one go, so how would you access the full Fetch Response if you wanted to read a response header for example? You'd use the onRequestComplete() hook, like this:

let pagesTotal = 0
const posts = await soFetch("/wp-json/wp/v2/posts").onRequestComplete((response:Response) => {
    postsTotal = parseInt(response.headers.get('X-WP-Total'))
})

File Upload

Most modern APIs separate sending files from form data so if you just want to send a file to an endpoint, soFetch makes it really easy. Just send a File instead of a serialisable object:

const file = new File(["File contents"], "test.txt", {type: "text/plain"})
const result = await soFetch("/api/files", file)

Sending an array of files

This example attaches a change event listener to an HTMLInputElement (assumed to be of type "file")

const input = document.querySelector<HTMLInputElement>("#fileInput");

input?.addEventListener("change", async () => {
    if (input.files) {
        await soFetch("/api/files", input.files)
    }
});

Files with Field Names

Sometimes the server endpoint expects the files to have field names. In this case you can post a FileWithFieldName, like this:

const file = new File(["File contents"], "test.txt", {type: "text/plain"})
const result = await soFetch("/api/files", {file:file, fieldName:"NAME_EXPECTED_BY_SERVER"})

Sending Files and Data Simultaneously

Sometimes an API endpoint will request files and data at the same time. Implementations of this type of endpoint vary considerably (so read your docs!) but typically you might use FormData instead of JSON to send data and files simultaneously. An example could look something like this:

const file = new File(["File contents"], "test.txt", {type: "text/plain"})
            
const result = await soFetch.put("/api/files-and-data").beforeFetchSend((init:RequestInit) => {
    const formData = new FormData()
    formData.append("username", "chris")
    formData.append("company", "Antoinette")
    
    //This bit removes the JSON content-type header, causing Fetch to default to 'multipart/form-data':
    const headers = {...init.headers} as Record<string,string>
    if (headers["content-type"]) {
        delete headers["content-type"]
    }
    
    init.body = formData
    init.headers = headers
    return init
})

Instances

Sometimes you might be communicating with 2 APIs at the same time, each with it's own configuration. soFetch lets you handle this scenario easily with it's instance() function.

//Create the first independent instance:
apiClient1 = soFetch.instance("CLIENT_1_KEY")
apiClient1.config.baseUrl = "https://api.example.com"
apiClient2.config.useCookieAuthentication()
const result1 = await apiClient1("/some-resource")

//And now the second:
const apiClient2 = soFetch.instance("CLIENT_2_KEY")
apiClient2.config.baseUrl = "https://some-other-service.com"
apiClient2.config.useBearerAuthentication()
const result2 = await apiClient2("/something-completely-different")

In the example above apiClient1 and apiClient2 are completely independent instances of soFetch:

apiClient1apiClient2
Authentication KeyCLIENT_1_KEYCLIENT_2_KEY
Base Urlhttps://api.example.comhttps://some-other-service.com
Authentication MethodCookie AuthenticationBearer Authentication

© 2025 AntoinettePrivacy