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.

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.
models/post.ts
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;
},
}) {}

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
Read the full guide on models

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.

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.

action.js
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;
Notice on blueprints

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.

import { makeJsonApi, jsonApiStarterExtensions } from 'foscia/blueprints';

const { action, registry } = makeJsonApi({
extensions: jsonApiStarterExtensions,
});

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());
Read the full guide on actions

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);