Getting started
- 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
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
- YARN
- PNPM
- Bun
npm install -D @foscia/cli && npx foscia init
yarn add -D @foscia/cli && yarn foscia init
pnpm add -D @foscia/cli && pnpm foscia init
bun add -D @foscia/cli && bun foscia init
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
- NPM
- YARN
- PNPM
- Bun
npx foscia make model post
yarn foscia make model post
pnpm foscia make model post
bun 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.
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
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:
- JSON:API
- JSON REST
import { makeActionFactory, makeCache } from '@foscia/core';
import {
makeJsonApiAdapter,
makeJsonApiDeserializer,
makeJsonApiSerializer,
} from '@foscia/jsonapi';
export default makeActionFactory({
...makeCache(),
...makeJsonApiDeserializer(),
...makeJsonApiSerializer(),
...makeJsonApiAdapter({
baseURL: '/api/v1',
}),
});
import { makeActionFactory, makeCache } from '@foscia/core';
import {
makeJsonRestAdapter,
makeJsonRestDeserializer,
makeJsonRestSerializer,
} from '@foscia/rest';
export default makeActionFactory({
...makeCache(),
...makeJsonRestDeserializer(),
...makeJsonRestSerializer(),
...makeJsonRestAdapter({
baseURL: '/api',
}),
});
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
.
- JSON:API
- JSON REST
import { makeActionFactory } from '@foscia/core';
import { jsonApiStarterExtensions } from '@foscia/jsonapi';
export default makeActionFactory(
{
// makeJsonApiAdapter(), ...etc.
},
jsonApiStarterExtensions,
);
import { makeActionFactory } from '@foscia/core';
import { jsonRestStarterExtensions } from '@foscia/rest';
export default makeActionFactory(
{
// makeJsonRestAdapter(), ...etc.
},
jsonRestStarterExtensions,
);
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);