Members area with profiles and a directory
Members area: the gate, member profiles, an opt-in member directory, groups with forum threads and an activity feed, courses with per-member progress, and direct messages with block and report
For when the answer to “People sign in to a members area: profiles, a member directory, and private groups with a forum and feed” is yes.
A members area is the place a paying or signed-up customer goes that the public does not.
Skip the rebuild
You do not write this one. It arrives in your project as ordinary source you can read, change and keep, with its audit record and its caveats beside it.
- Download the Playground. It is free and runs on your own machine.
- Start a new app and tick “People sign in to a members area: profiles, a member directory, and private groups with a forum and feed”.
- This block is written in, along with anything else you ticked.
The mistake it removes
Five, and all are quiet. A members gate with no real identity behind it denies nothing while looking complete, so the gate refuses to be built without the host app's own account lookup and stays closed when that lookup breaks. A directory that lists every member publishes a list of your customers, so being listed is opt-in and a profile is shown through an allowlist. And a group post shown to anyone outside the group is a private conversation leaked: every read and write in community.js checks membership on the server at the moment of the request, the home feed is built only from the caller's own groups, an invite-only group answers a stranger exactly as a missing one does, and the handlers take the account from the gate and ignore any accountId a caller writes into the request. Courses follow the same rule: a lesson is readable only by someone the course's gate lets in, checked at every request, a hidden course answers exactly like a missing one, an entitlement course with no entitlement check lets nobody in, and progress is keyed by the member's own account with no way to ask for anyone else's. And a direct message shown to anyone but its two participants is the most private leak of all: every read in messages.js checks that the caller is one of the two, an outsider is answered exactly as if the conversation did not exist, a member can open a conversation only with someone they share a group with, a block stops new messages both ways without announcing itself, and the notifier the host passes never receives the words.
What you still have to do
A copied file cannot own your secrets, your host or your legal obligations. This is the part the block deliberately does not claim.
- A request limiter in front of every handler, per IP or per session. This block limits each member: posting per group (test/community.test.mjs section 10), and creating groups, joining, inviting and reading (section 11), creating courses and reading courses (test/courses.test.mjs section 8), and sending and reading direct messages (test/messages.test.mjs section 11). Those counts are per account, so one person with many accounts gets many allowances, and on a store without transactions a burst of simultaneous requests can slip a little past a count. Adding and completing lessons, blocking and reporting are not limited here.
- A real database behind the four store methods. memoryStore() keeps groups, posts, courses and progress in process memory and loses all of them on a restart or a new serverless instance; it exists for tests and local development only.
What lands in your project
node
- node/members.js
- node/community.js
- node/courses.js
- node/messages.js
No package is imported and no database is assumed. members.js is pure functions: requireMember(resolveAccount) builds the gate from the host app's own account lookup; publicProfile, directory and canEditProfile take plain objects. community.js takes a store with four async methods (get, set, del, list by prefix), ships memoryStore() as a reference for tests and local development, exports communityHandlers({ gate, community }) as framework-free route handlers, and limits posting to postLimit ({ max, windowMs }, default 10 in 60 seconds) per member per group. Every service also takes limits: per-member sliding windows on group creation, joins, invites and reads (community.js), course creation and reads (courses.js), and sending and reads (messages.js), with the defaults exported as DEFAULT_LIMITS, DEFAULT_COURSE_LIMITS and DEFAULT_MESSAGE_LIMITS. courses.js takes the same store plus the host's optional hasEntitlement(account, entitlement) and canAuthor(account), and exports courseHandlers({ gate, courses }). messages.js takes the same store plus a REQUIRED notify({ toAccountId, fromAccountId, conversationId, messageId }), which never carries the message text, and exports messageHandlers({ gate, messaging }); reports() is for the host's moderators and has no route handler.
What you supply
- resolveAccount — the host app's function from a request to { accountId, ... } or null — the account (F48) the members area keys on; there is no login in this block
- listed — a per-profile boolean the member sets; only true appears in the directory
- store — the host's store for groups, memberships, posts, courses, lessons and progress: an object with async get(key), set(key, value), del(key) and list(prefix). memoryStore() loses everything on restart and is for tests and local development only
- postLimit — { max, windowMs }: how many posts one member may write in one group within a sliding window. Default 10 in 60 seconds. It cannot be switched off
- limits — per service, an object of { max, windowMs } by name, each a per-member sliding window. createCommunity: createGroup (default 5 an hour), join (30 an hour), invite (50 an hour), read (300 a minute). createCourses: createCourse (10 an hour), read (300 a minute). createMessaging: send, counting sends and replies together (20 a minute), read (300 a minute). A name left out keeps its default; none can be switched off
- hasEntitlement — the host app's async function (account, entitlement) -> true when the account holds it, for courses gated to an entitlement such as a paid plan. Without it an entitlement course lets nobody in, and one that throws answers 503
- canAuthor — the host app's async function (account) -> true for accounts allowed to create entitlement courses. Group courses are written by the group's owner and moderators
- notify — the host app's async function that tells a member a message arrived (an email, a push). Required; pass one that does nothing to send no notifications. It gets ids only, never the words, and one that throws leaves the message sent
Licensed MIT. It is a starting point, not a finished product.
Get it
Download the Playground See the other blocks
Nothing here is locked. The files are yours, in your folder, under a permissive license.
Questions
- Is Members area with profiles and a directory audited?
- No. No full audit has been run against this block yet, which is not the same as a pass. What is verified about it is listed on this page, and what is not is listed beside it.
- What do I still have to do myself?
- A request limiter in front of every handler, per IP or per session. This block limits each member: posting per group (test/community.test.mjs section 10), and creating groups, joining, inviting and reading (section 11), creating courses and reading courses (test/courses.test.mjs section 8), and sending and reading direct messages (test/messages.test.mjs section 11). Those counts are per account, so one person with many accounts gets many allowances, and on a store without transactions a burst of simultaneous requests can slip a little past a count. Adding and completing lessons, blocking and reporting are not limited here.
- How do I get this code?
- Download the Playground, start a new app, and tick “People sign in to a members area: profiles, a member directory, and private groups with a forum and feed”. The block is written into your project as ordinary source you can read and edit.
All pre-built code blocks · Learn to build from zero · The coding guide