JSON:API blog
- Defining blog and tag models
- Use blog model for CRUD operations on a JSON:API backend
This is a simple example to implement a blog content management using Foscia. This example is framework-agnostic, so you'll only see examples of models or actions calls. You may use those examples inside any project (Vanilla JS, React, Vue, etc.).
Models
import { attr, makeModel } from '@foscia/core';
export default class Tag extends makeModel('tags', {
name: attr<string>(),
}) {}
import { attr, hasMany, makeModel, toDateTime } from '@foscia/core';
import Tag from './tag';
export default class Post extends makeModel('posts', {
title: attr<string>(),
description: attr<string>(),
publishedAt: attr(toDateTime()).nullable(),
tags: hasMany(() => Tag),
get published() {
return !!this.publishedAt;
},
}) {}
Action
import { makeActionFactory, makeCache } from '@foscia/core';
import {
makeJsonApiAdapter,
makeJsonApiDeserializer,
makeJsonApiSerializer,
} from '@foscia/jsonapi';
export default makeActionFactory({
...makeCache(),
...makeJsonApiDeserializer(),
...makeJsonApiSerializer(),
...makeJsonApiAdapter({
baseURL: '/api/v1',
}),
});
Classic CRUD
View many
import { query, when, all } from '@foscia/core';
import { filterBy, sortByDesc, paginate, usingDocument } from '@foscia/jsonapi';
import action from './action';
import Post from './models/post';
export default async function fetchAllPost(query = {}) {
return action().run(
query(Post),
when(query.search, (a, s) => a.use(filterBy('search', s))),
sortByDesc('createdAt'),
paginate({ number: query.page ?? 1 }),
all(usingDocument),
);
}
const { instances, document } = await fetchAllPost({ search: 'Hello' });
View one
import { query, include, oneOrFail } from '@foscia/core';
import action from './action';
import Post from './models/post';
export default async function fetchOnePost(id) {
return action().run(query(Post, id), include('tags'), oneOrFail());
}
const post = await fetchOnePost('123-abc');
Create or update one
import { changed, fill, oneOrCurrent, reset, save, when } from '@foscia/core';
import action from './action';
import Post from './models/post';
export default async function savePost(post, values = {}) {
fill(post, values);
try {
await action().run(
save(post),
when(!post.$exists || changed(post), oneOrCurrent(), () => instance),
);
} catch (error) {
reset(post);
throw error;
}
return post;
}
const post = new Post();
await savePost(post, {
title: 'Hello World!',
publishedAt: new Date(),
});
Delete one
import { destroy, none } from '@foscia/core';
import action from './action';
import Post from './models/post';
export default async function deletePost(post) {
await action().run(destroy(post), none());
}
const post = new Post();
await deletePost(post);
Non-standard actions
You can also use Foscia to run non-standard actions to your backend.
Thanks to functional programming, you can easily combine non-standard action with classical context enhancers and runners.
import { query, include, when, all, oneOrFail } from '@foscia/core';
import { makeGet, makePost } from '@foscia/http';
import action from './action';
import Post from './models/post';
export default function bestPosts() {
return action().run(
query(Post),
.makeGet('actions/best-posts'),
all(),
);
}
export default function publishPost(post, query = {}) {
return action().run(
query(post),
when(query.include, (a, i) => a.use(include(i))),
makePost('actions/publish', {
publishedAt: new Date(),
}),
oneOrFail(),
);
}
// Sends a GET to "<your-base-url>/posts/actions/best-posts
// and deserialize a list of Post instances.
const posts = await bestPosts();
const post = new Post();
// Sends a POST to "<your-base-url>/posts/<id>/actions/publish
// and deserialize a Post instance.
await publishPost(post);
makeGet
or other custom request enhancers (makePost
, etc.) will just append
the given path if it is not an "absolute" (starting with a scheme such as
https://
) path. This allows you to run non-standard actions scoped to an
instance, etc.
Your may also use an absolute (starting with a scheme) path like
https://example.com/some/magic/action
to ignore the configured base URL and
run a non-standard action.