npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@molecule/api-resource-follow

v1.0.1

Published

Follow/unfollow users or any resource with polymorphic targeting

Readme

@molecule/api-resource-follow

Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit src/index.ts JSDoc, not this file.

Follow/unfollow resource for molecule.dev.

Polymorphic follow system for users or any resource type. Supports followers list, following list, and follow status checks.

Quick Start

import { routes, requestHandlerMap } from '@molecule/api-resource-follow'

// Wire routes into your Express app via mlcl inject
// POST   /follow/:targetType/:targetId
// DELETE /follow/:targetType/:targetId
// GET    /:targetType/:targetId/followers
// GET    /following
// GET    /follow/check/:targetType/:targetId

Type

resource

Installation

npm install @molecule/api-resource-follow @molecule/api-database @molecule/api-i18n @molecule/api-logger @molecule/api-resource

API

Interfaces

Follow

A follow relationship between a user and a target resource.

interface Follow {
  /** Unique follow identifier. */
  id: string
  /** The ID of the user who is following. */
  followerId: string
  /** The type of target being followed (e.g. 'user', 'project'). */
  targetType: string
  /** The ID of the target being followed. */
  targetId: string
  /** When the follow was created (ISO 8601). */
  createdAt: string
  /** When the follow was last updated (ISO 8601). */
  updatedAt: string
}

PaginatedResult

A paginated result set.

interface PaginatedResult<T> {
  /** The result items for the current page. */
  data: T[]
  /** Total number of matching items across all pages. */
  total: number
  /** Maximum number of results per page. */
  limit: number
  /** Number of results skipped. */
  offset: number
}

PaginationOptions

Options for paginated queries.

interface PaginationOptions {
  /** Maximum number of results to return. */
  limit?: number
  /** Number of results to skip. */
  offset?: number
}

Functions

checkFollowing(req, res)

Checks if the current user is following a target.

function checkFollowing(req: MoleculeRequest, res: MoleculeResponse): Promise<void>
  • req — The request with targetType and targetId params.
  • res — The response object.

create(req, res)

Creates a follow relationship. Idempotent.

function create(req: MoleculeRequest, res: MoleculeResponse): Promise<void>
  • req — The request with targetType and targetId params.
  • res — The response object.

del(req, res)

Removes a follow relationship.

function del(req: MoleculeRequest, res: MoleculeResponse): Promise<void>
  • req — The request with targetType and targetId params.
  • res — The response object.

follow(followerId, targetType, targetId)

Creates a follow relationship. Idempotent — returns existing follow if already following.

function follow(followerId: string, targetType: string, targetId: string): Promise<Follow>
  • followerId — The ID of the follower.
  • targetType — The type of target being followed.
  • targetId — The ID of the target being followed.

Returns: The created or existing follow.

following(req, res)

Lists targets the current user is following.

function following(req: MoleculeRequest, res: MoleculeResponse): Promise<void>
  • req — The request with pagination query params.
  • res — The response object.

getFollowerCount(targetType, targetId)

Gets the follower count for a target.

function getFollowerCount(targetType: string, targetId: string): Promise<number>
  • targetType — The type of target.
  • targetId — The ID of the target.

Returns: The number of followers.

getFollowers(targetType, targetId, options)

Gets paginated followers of a target.

function getFollowers(
  targetType: string,
  targetId: string,
  options?: PaginationOptions,
): Promise<PaginatedResult<Follow>>
  • targetType — The type of target.
  • targetId — The ID of the target.
  • options — Pagination options.

Returns: Paginated followers.

getFollowing(userId, options)

Gets paginated targets a user is following.

function getFollowing(userId: string, options?: PaginationOptions): Promise<PaginatedResult<Follow>>
  • userId — The follower's user ID.
  • options — Pagination options.

Returns: Paginated follows.

getFollowingCount(userId)

Gets the following count for a user.

function getFollowingCount(userId: string): Promise<number>
  • userId — The user ID.

Returns: The number of targets the user is following.

isFollowing(followerId, targetType, targetId)

Checks if a user is following a target.

function isFollowing(followerId: string, targetType: string, targetId: string): Promise<boolean>
  • followerId — The follower's user ID.
  • targetType — The type of target.
  • targetId — The ID of the target.

Returns: true if following.

list(req, res)

Lists paginated followers of a target.

function list(req: MoleculeRequest, res: MoleculeResponse): Promise<void>
  • req — The request with targetType and targetId params.
  • res — The response object.

unfollow(followerId, targetType, targetId)

Removes a follow relationship.

function unfollow(followerId: string, targetType: string, targetId: string): Promise<void>
  • followerId — The ID of the follower.
  • targetType — The type of target.
  • targetId — The ID of the target.

Constants

requestHandlerMap

Handler map for follow routes.

const requestHandlerMap: {
  readonly create: typeof create
  readonly del: typeof del
  readonly list: typeof list
  readonly following: typeof following
  readonly checkFollowing: typeof checkFollowing
}

routes

Routes for follow/unfollow, followers, following, and status check.

const routes: readonly [
  {
    readonly method: 'post'
    readonly path: '/follow/:targetType/:targetId'
    readonly handler: 'create'
    readonly middlewares: readonly ['authenticate']
  },
  {
    readonly method: 'delete'
    readonly path: '/follow/:targetType/:targetId'
    readonly handler: 'del'
    readonly middlewares: readonly ['authenticate']
  },
  {
    readonly method: 'get'
    readonly path: '/:targetType/:targetId/followers'
    readonly handler: 'list'
  },
  {
    readonly method: 'get'
    readonly path: '/following'
    readonly handler: 'following'
    readonly middlewares: readonly ['authenticate']
  },
  {
    readonly method: 'get'
    readonly path: '/follow/check/:targetType/:targetId'
    readonly handler: 'checkFollowing'
    readonly middlewares: readonly ['authenticate']
  },
]

Injection Notes

Requirements

Peer dependencies:

  • @molecule/api-database ^1.0.1
  • @molecule/api-i18n ^1.0.1
  • @molecule/api-logger ^1.0.1
  • @molecule/api-resource ^1.0.1

Runtime Dependencies

  • @molecule/api-database

  • @molecule/api-i18n

  • @molecule/api-logger

  • @molecule/api-resource

  • List endpoints return a PAGINATED envelope { data, total, limit, offset }, not a bare array — read the rows off result.data (server). On the client, unwrapList(res) from @molecule/app-http normalizes this envelope (pass it the whole HttpResponse), so the rows come back; reading the response as a bare array — or res.data alone (which is the envelope) — yields an EMPTY list. Table: src/__setup__/follows.sql creates the single follows table. An mlcl-scaffolded API replays __setup__/*.sql automatically on migrate; anywhere else run it once — nothing at runtime creates it.

The follower is ALWAYS the authenticated user: handlers read res.locals.session (populated by your global auth middleware) and 401 without it — never accept a follower userId from the body or params. GET /:targetType/:targetId/followers is deliberately PUBLIC; gate it yourself if follower lists are private in your app.

targetType is a free-form string (user, post, …) — the package does not validate it against your schema, so constrain accepted values in your app if arbitrary types would be a problem.

E2E Tests

Integration checklist — drive the real UI (live preview, no mocks), adapt each item to this app's actual screens/flows, and check every box off one by one. A box you can't check is an integration bug to fix — not a skip:

  • [ ] User A follows user B (POST /follow/user/:B): B's follower count and A's following count each increment by exactly one, B appears in A's following list (GET /following) and A appears in B's followers list (GET /user/:B/followers). Reload — the edge and both counts persist (it's a real follows row, not local UI state).
  • [ ] Following is IDEMPOTENT: A following B a second time (double-tap Follow or replay the POST) creates NO duplicate edge and does NOT double-count — exactly one follows row exists for (A → B) and both counts are unchanged.
  • [ ] Unfollow (DELETE /follow/user/:B) removes the edge: A's following count and B's follower count each decrement back, B leaves A's following list, A leaves B's followers, and GET /follow/check/user/:B now returns { following: false }.
  • [ ] You cannot follow yourself: the UI never offers Follow on your own profile, and following your own id never inflates your own counts. This package's follow() does not reject followerId === targetId, so the app must guard it — verify the guard exists, don't assume it.
  • [ ] AUTHORIZATION — the follower is ALWAYS the session user: handlers read res.locals.session and 401 without it, so no UI or endpoint lets you follow/unfollow on behalf of another user by passing their id, and follow/unfollow act only on your own edges. Signed in as A you can never make B follow or unfollow anyone.