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.
CLI
You can get started for Foscia using the Foscia CLI. It is available on any project where Foscia package is installed.
# Initialize Foscia and action factory inside "src/api" directory.
foscia init src/api
# Create your first model.
foscia make:model post
Next sections of this guide explain how Foscia works and how you can set up files without using the CLI.
Your 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
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.
- TypeScript
- JavaScript
import { makeModel, attr, hasMany, toDate } from 'foscia/core';
import type Comment from './comment';
export default class Post extends makeModel('posts', {
title: attr<string>(),
description: attr<string>(),
publishedAt: attr<Date | undefined>(toDate()),
comments: hasMany<Comment>(),
get published() {
return !!this.publishedAt;
},
}) {}
import { makeModel, attr, hasMany, toDate } from 'foscia/core';
export default class Post extends makeModel('posts', {
title: attr(),
description: attr(),
publishedAt: attr(toDate()),
comments: hasMany(),
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.
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
Your first actions
Action factory
With blueprints
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.
Blueprints provide a quick initialization of your action factory for different common use cases. Blueprints will also preconfigure registered models for those use cases.
Currently, you may choose between the two available blueprints: JSON:API or JSON REST.
- JSON:API
- JSON REST
- Other
foscia init src/api --usage=jsonapi
makeJsonApi
is a blueprint to quickly initiate an action factory to interact
with a normalized JSON:API backend. It provides all the
available tooling, such as a model registry, an instance cache, a serializer and
deserializer and an adapter.
You can read more details on the JSON:API implementation page.
import { makeJsonApi } from 'foscia/blueprints';
import Comment from './models/comment';
import Post from './models/post';
const { action, registry } = makeJsonApi({
baseURL: 'https://example.com/api/v1',
});
// We need to register the models to allow the deserializer to know
// to which model it should deserialize from an API record.
registry.register(Comment, Post);
export default action;
foscia init src/api --usage=jsonrest
makeJsonRest
is a blueprint to quickly initiate an action factory to interact
with a normalized REST backend exchanging JSON.
It provides all the available tooling, such as a model registry, an instance
cache, a serializer and deserializer and an adapter.
You can read more details on the REST implementation page.
import { makeJsonRest } from 'foscia/blueprints';
import Comment from './models/comment';
import Post from './models/post';
const { action, registry } = makeJsonRest({
baseURL: 'https://example.com/api',
});
// We need to register the models to allow the deserializer to know
// to which model it should deserialize from an API record.
registry.register(Comment, Post);
export default action;
For the moment, there is no other official implementation available besides JSON:API and REST. 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.
Using blueprints is a simple and quick way to set up an action factory. However, it may have some downsides:
- Only some implementations are available for now, so you may not find something which fits your needs
- Some dependencies (e.g. serializer in a readonly context) may be imported even if you don't use them, and it may increase your production bundle size
- Some behaviors may not be configurable
You may configure the behavior or implementation in two ways:
Builder pattern syntax
If you want to use the builder pattern syntax (e.g. action().forModel(Post)
instead of action().use(forModel(Post))
), you must provide the extensions
you want to use during your action factory creation.
- JSON:API
- JSON REST
import { makeJsonApi, jsonApiStarterExtensions } from 'foscia/blueprints';
const { action, registry } = makeJsonApi({
extensions: jsonApiStarterExtensions,
});
import { makeJsonRest, jsonRestStarterExtensions } from 'foscia/blueprints';
const { action, registry } = makeJsonRest({
extensions: jsonRestStarterExtensions,
});
You can learn more about extensions, check the available extensions packs or import manually enhancers and runners extensions using the following code:
import { forModel, include, all } from 'foscia/core';
import { makeJsonApi, hooksExtensions } from 'foscia/blueprints';
const { action, registry } = makeJsonApi({
extensions: {
...hooksExtensions,
...forModel.extension,
...include.extension,
...all.extension,
},
});
Running simple actions
To run an action, you can initialize a new action instance by calling your
factory. With this instance, you can call context enhancers through use
to
modify the action context. When you are ready, you can run
the action with a
given context runner.
import { all, forModel } from 'foscia/core';
import Post from './models/post';
import action from './action';
const posts = await action().use(forModel(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 find
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 { find, oneOrFail } from 'foscia/core';
import Post from './models/post';
import action from './action';
const post = await action().use(find(Post, 'abc-123')).run(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 response = await action().use(create(post)).run(oneOrCurrent());
Running HTTP custom actions
Using JSON:API or REST blueprints, you can also use Foscia to make non-standard API calls.
This way, you can standardize all API calls across your application, even when those are non JSON:API/REST related.
import { forInstance, raw } from 'foscia/core';
import { makePost } from 'foscia/http';
import Comment from './comment';
import action from './action';
const comment = await action().use(find(Comment, 'abc-123')).run(oneOrFail());
// Make a POST call to "https://example.com/api/v1/services/comments/1/publish"
const response = await action()
.use(forInstance(comment))
.use(
makePost('publish', {
data: { publishedAt: new Date() },
}),
)
.run(raw());
// This is a raw `fetch` Response object.
console.log(response.status);