BTST
PluginsQuickstartDocs
Live Blog

From research to product evaluation

Evaluating a publishing workflow for an app you already own?

See what the BTST Blog plugin adds to an existing React or Next.js app
BTST

Open-source TypeScript features for the React application, data, and deployment you already own.

Released plugins

  • Blog
  • AI Chat
  • CMS
  • Form Builder
  • UI Builder
  • Kanban
  • Comments
  • Media
  • Route Docs
  • OpenAPI
  • Better Auth UI

Resources

  • Quickstart
  • Documentation
  • All plugins
  • Live Blog
  • GitHub (opens in a new tab)
© 2026 BTST. Open source under the MIT License.
November 21, 2025Web Development

Modular Full-Stack Development in React with BTST

See how BTST composes backend and client plugins, with tested v3 examples and clear responsibilities for storage, permissions, routing and publishing.

Modular Full-Stack Development in React with BTST

A modular full-stack feature packages related database models, server operations and user interfaces so you can add them to an existing application. The useful question is how much integration work it removes, and which responsibilities remain with your team.

For a React application, a Blog feature is a concrete example. An editor component covers writing. A complete feature also needs post storage, draft rules, mutation endpoints, public routes, metadata and cache updates. Evaluating those pieces together gives you a better basis for choosing a library than a promise of “zero glue code.”

This guide uses BTST 3.1.2 to show that division. It replaces the older examples on this page, which used obsolete APIs and included hypothetical newsletter and roadmap plugins. Those examples were not a working installation.

Choose what to reuse#

Different approaches transfer different maintenance work to your application:

ApproachWhat you reuseWhat you maintain
UI componentsEditor, forms and presentationData model, API, access policy and routes
Starter or generated codeAn initial implementationCopied code and its future changes
Full-stack pluginRelated server operations and client behaviorApplication integration, configuration and upgrades
Hosted content serviceA separate content backendService integration, access configuration and delivery

A plugin fits when its content model and workflow are close to your requirements. If your publishing rules are unusual, inspect the extension points before adopting it. A small editor plus your own API can be easier to maintain than extensive overrides of an incompatible feature.

AI tools can help write either integration. Generated code still needs review against the installed package version, your access rules and your deployment environment. Reuse and generation can be combined; neither establishes that an application is ready for production.

Start with a real feature#

BTST separates backend plugins from client plugins. The Blog backend supplies post and tag operations. The Blog client supplies pages and data loading. Your framework connects them to HTTP requests and rendered routes.

Here is the backend composition with the published 3.1.2 API:

TS
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
// lib/stack.ts — local demonstration only
import { createBackendStack } from "@btst/stack/api"
import { createMemoryAdapter } from "@btst/adapter-memory"
import { blogBackendPlugin } from "@btst/stack/plugins/blog/api"

export const stack = createBackendStack({
  basePath: "/api/data",
  plugins: {
    blog: blogBackendPlugin(),
  },
  adapter: (schema) => createMemoryAdapter(schema)({}),
})

export const { handler, dbSchema } = stack

This illustrates composition, not a production backend. The memory adapter does not provide durable storage. This example also supplies no application authorization policy: do not expose its mutation endpoints publicly. Connect your database and server access checks before deploying.

The client configuration uses the corresponding client API:

TS
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
// lib/stack-client.ts
import { createClientStack } from "@btst/stack/client"
import { blogClientPlugin } from "@btst/stack/plugins/blog/client"
import type { QueryClient } from "@tanstack/react-query"

export function getStackClient(queryClient: QueryClient) {
  return createClientStack({
    api: {
      baseURL: "http://localhost:3000",
      basePath: "/api/data",
    },
    site: {
      baseURL: "http://localhost:3000",
      basePath: "/pages",
    },
    queryClient,
    plugins: {
      blog: blogClientPlugin(),
    },
  })
}

These snippets were type-checked against Stack 3.1.2. They describe two parts of the integration; importing them does not mount a route or render a page. Supply trusted deployment origins instead of the local URLs. Create a query client for each server request so one visitor's prefetched data is not reused for another visitor.

Use the framework guide to connect the HTTP handler, page routes, provider, styles and hydration: Next.js, TanStack Start or React Router. The Blog documentation describes the feature surface; match code examples to your installed release.

Keep application boundaries explicit#

A reusable feature still depends on decisions that only your application can make.

Storage and migrations. Choose an adapter, generate or reconcile its schema, and apply reviewed migrations. Decide how backups and restoration work. A plugin's model definitions do not migrate a production database merely because you import them.

Identity and permissions. Decide who may read drafts, create posts, edit another author's post and publish. Enforce these decisions on the server. Hiding an editor button is not authorization. BTST's authorization guide explains its access configuration; the application supplies the identity and policy.

Files. Choose storage, validate uploads and enforce access at the upload endpoint. If you use the Media plugin, verify both the asset record and stored bytes. An object-storage URL alone does not establish that an asset is registered in your application's Media library.

Rendering and caches. A page loader can prefetch content, but your framework still needs to render the route and transfer the appropriate query state. Publishing must refresh the relevant list, article and metadata caches. Server rendering and hydration require deliberate configuration; they do not guarantee the absence of every loading state. See TanStack Query's server-rendering guidance.

Public URLs. Decide the page prefix and canonical origin. Check actual canonical links, social metadata and sitemap entries after deployment. An API mounted at /api/data and pages mounted at /pages have different jobs; their paths need to agree with the client configuration.

Verify one complete workflow#

Evaluate a candidate plugin with the same small workflow you expect users to perform:

  1. Create a draft as an authorized editor and confirm an anonymous reader cannot retrieve it.
  2. Publish it and open its public URL without an editor session.
  3. Confirm the title and content appear in server-rendered HTML, and verify the canonical URL and feature image.
  4. Edit the post and confirm both the article and listing reflect the change after the intended cache refresh.
  5. Restart the application and verify that the published content persists.
  6. Try an unauthorized mutation directly against the API and confirm the server rejects it.

These checks cover different boundaries. A successful TypeScript build cannot prove your database persists data. A working editor cannot prove anonymous access is restricted. A database write cannot prove a cached public page has updated.

The snippets above were checked as isolated configuration examples; they are not evidence that your authentication, uploads, migrations or deployment have been tested.

Add plugins as needs arise#

Start with the feature you need now. Add a second plugin when its behavior solves a concrete requirement, then verify how identity, routes and data ownership interact. Similar naming conventions do not guarantee that independently configured plugins share an access policy or connect their records automatically.

For BTST, compare the available plugins with your requirements, then follow installation for your framework. Keep the backend/client boundary visible and test the resulting user workflow. That is the practical value of modular full-stack development: reuse established feature behavior while making your application's responsibilities explicit.

In This Post

Choose what to reuseStart with a real featureKeep application boundaries explicitVerify one complete workflowAdd plugins as needs arise