Skip to main content

Getting started

What you'll learn
  • Creating your first model and using it through model instances
  • Creating your action factory using blueprints
  • Running basic actions on your models with your action factory
info

Only here for a simple HTTP client? Check out the simple HTTP client guide.

Before getting started

Before getting started, you must have installed Foscia packages you will need. @foscia/cli is the recommended way to install and get started using Foscia.

npm install -D @foscia/cli && npx foscia init
info

Since Foscia is not installed globally, you will need to prefix foscia with your package runner.

Project structure

Foscia files are organized in an opinionated manner. This allow Foscia to provide better features when using the CLI.

You can learn more about how Foscia files are organized in your project within the project structure guide.

Supported data sources

For the moment, there is no other official implementation available besides JSON:API and REST. If you would like to interact with another data source, you should therefore either:

  • Implement your own action's dependencies, you should inspire from existing implementations to build your own.
  • Open an issue or a pull request to suggest a new implementation.

Supported frameworks

Foscia can integrate in pretty any TypeScript/JavaScript projects. That being said, supporting some frameworks features may require additional configuration (such as SSR inside Nuxt). You can find more information about frameworks integrations inside the integrations guides.

If you need specific features to integrate with an unsupported framework, feel free to open an issue or a pull request.

First model

Models represent the structure of your data and are used to simplify and get type safe interactions with your data source.

Defining a model

npx foscia make model post

To declare a model, you just need to use the makeModel function. This function takes up to 2 arguments and returns an ES6 class:

  • The model type, which is used by other services to identify your model or interact with a data source.
  • The model definition, which contains your attributes/relations definitions and custom properties and methods.
models/post.ts
import { makeModel, attr, hasMany, toDateTime } from '@foscia/core';
import Comment from './comment';

export default class Post extends makeModel('posts', {
title: attr<string>(),
description: attr<string>(),
publishedAt: attr(toDateTime()).nullable(),
comments: hasMany(() => Comment),
get published() {
return !!this.publishedAt;
},
}) {}

Using models classes

Model classes can be used like any ES6 class. It can be instantiated, manipulated, etc. Properties will be defined on each instance from the model definition.

import Post from './models/post';

const post = new Post();
post.title = 'Hello World!';
post.publishedAt = new Date();
console.log(post.title); // "Hello World!"
console.log(post.published); // true
console.log(post.$exists); // false

Read the full guide on models

First actions

Action factory

Creating the action factory

info

If you used @foscia/cli, your action factory should already be set up.

Once your models are set up, you will probably want to interact with a data source, such as an API. For this, you will need an action factory which initialize a preconfigured context for all your future actions. Running actions using this action factory will be seen in the next part of this guide.

Action factory will set up all the required context for interacting with data source (adapter, (de)serializer, etc.).

To quickly get started, multiple Foscia packages provide blueprint factories which provide a quick initialization of this action factory with sensible defaults.

Depending on the usage you are targetting, here is what your action factory may looks like:

import { makeActionFactory, makeCache } from '@foscia/core';
import {
makeJsonApiAdapter,
makeJsonApiDeserializer,
makeJsonApiSerializer,
} from '@foscia/jsonapi';

export default makeActionFactory({
...makeCache(),
...makeJsonApiDeserializer(),
...makeJsonApiSerializer(),
...makeJsonApiAdapter({
baseURL: '/api/v1',
}),
});

Builder pattern syntax

If you want to use the builder pattern syntax (e.g. action().query(Post).all() instead of action().run(query(Post), all())), you must provide the extensions you want to use during your action factory creation.

Extensions are the second argument passed to makeActionFactory.

import { makeActionFactory } from '@foscia/core';
import { jsonApiStarterExtensions } from '@foscia/jsonapi';

export default makeActionFactory(
{
// makeJsonApiAdapter(), ...etc.
},
jsonApiStarterExtensions,
);
warning

Be aware that using extensions and builder pattern syntax might increase your bundle size, because functions are imported even if you are not using it.

Running simple actions

To run an action, you can initialize a new action instance by calling your factory. With this instance, you can provide context enhancers to modify the action context, and a context runner to run the action.

Simplest way of running an action is to call run, providing enhancers as argument and providing a runner as last argument. This is called the variadic run calls. Documentation is generally using this call format.

You can also decompose those calls through use and run individual calls, which will better show how Foscia actions works.

import { all, query } from '@foscia/core';
import Post from './models/post';
import action from './action';

// Variadic run.
const posts = await action().run(
query(Post),
all(),
);
// Equivalent: individual calls.
const posts = await action()
.use(query(Post))
.run(all());

In Foscia, the context enhancers are doing the majority of work to customize the action you will run. Context runners only exists to tell how you wish to run the action and retrieve the result (raw result, model instance, etc.).

A great example of this is when finding a model using its ID. You'll not use a "find" context runner. Instead, you will need to use a query context enhancer and a oneOrFail context runner. This way, you are able to do a find query and retrieve a raw result when needed.

import { query, oneOrFail } from '@foscia/core';
import Post from './models/post';
import action from './action';

const post = await action().run(
query(Post, 'abc-123'),
oneOrFail(),
);

This works the same to send write operations through actions. In the following example, we are retrieving a raw adapter response instead of model instances.

import { create, fill, oneOrCurrent } from '@foscia/core';
import Post from './models/post';
import action from './action';

const post = fill(new Post(), {
title: 'Hello World!',
description: 'Your first post',
});

const createdPost = await action().run(
create(post),
oneOrCurrent(),
);

Read the full guide on actions

Advanced actions

HTTP custom actions

Using JSON:API or REST blueprints, you can also use Foscia to make non-standard (non-CRUD) API calls.

This way, you can standardize all API calls across your application, even when those are non JSON:API/REST related.

import { query, raw, oneOrFail } from '@foscia/core';
import { makePost } from '@foscia/http';
import Comment from './comment';
import action from './action';

const comment = await action().run(
query(Comment, '1'),
oneOrFail(),
);

// Make a POST call to "https://example.com/api/v1/comments/1/publish"
const response = await action().run(
query(comment),
makePost('publish', {
data: { publishedAt: new Date() },
}),
raw(),
);

// This is a raw `fetch` Response object.
console.log(response.status);