JSON:API blog CRUD
- 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
- TypeScript
- JavaScript
import { attr, hasMany, makeModel } from 'foscia/core';
import type Post from './post';
export default class Tag extends makeModel('tags', {
name: attr<string>(),
posts: hasMany<Post>(),
}) {}
import { attr, hasMany, makeModel } from 'foscia/core';
export default class Tag extends makeModel('tags', {
name: attr(),
posts: hasMany(),
}) {}
- TypeScript
- JavaScript
import { attr, hasMany, makeModel, toDate } from 'foscia/core';
import type Tag from './tag';
export default class Post extends makeModel('posts', {
title: attr<string>(),
description: attr<string>(),
publishedAt: attr(toDate()),
tags: hasMany<Tag>(),
get published() {
return !!this.publishedAt;
},
}) {}
import { attr, hasMany, makeModel, toDate } from 'foscia/core';
export default class Post extends makeModel('posts', {
title: attr(),
description: attr(),
publishedAt: attr(toDate()),
tags: hasMany(),
get published() {
return !!this.publishedAt;
},
}) {}
Classic CRUD
View many
import { forModel, 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()
.use(forModel(Post))
.use(when(query.search, (a, s) => a.use(filterBy('search', s))))
.use(sortByDesc('createdAt'))
.use(paginate({ number: query.page ?? 1 }))
.run(all(usingDocument));
}
const { instances, document } = await fetchAllPost({ search: 'Hello' });
View one
import { find, include, oneOrFail } from 'foscia/core';
import action from './action';
import Post from './models/post';
export default async function fetchOnePost(id) {
return action().use(find(Post, id)).use(include('tags')).run(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()
.use(save(post))
.run(
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().use(destroy(post)).run(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 { forModel, forInstance, include, when, 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()
.use(forModel(Post))
.use(makeGet('actions/best-posts'))
.run(all());
}
export default function publishPost(post, query = {}) {
return action()
.use(forInstance(post))
.use(when(query.include, (a, i) => a.use(include(i))))
.use(makePost('actions/publish', {
publishedAt: new Date(),
}))
.run(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.