MongoDB Atlas Backend / REST API
Category: Backend & APIs ยท ๐ฆ 1 install
This page is generated from the Air Pipe marketplace. Browse it live to install into your organization.
A production-shaped REST API over a MongoDB Atlas database โ CRUD for users, posts, and categories, optional JWT validation, and a set of aggregation-pipeline analytics endpoints. No ORM, no Mongoose, no backend boilerplate: point AirPipe at your Atlas connection string and you have an API.
Already on the Next.js + MongoDB stack? This is the missing backend. Deploy it over your existing Atlas cluster and start making requests in under 10 minutes.
What's includedโ
| File | Purpose |
|---|---|
users.yml | User CRUD โ bcrypt-hashed passwords, passwordHash never returned |
posts.yml | Post CRUD โ slug-keyed, with author/category/tags/rating |
categories.yml | Category list / get / create / delete |
analytics.yml | Aggregation endpoints ($group / $unwind / $avg / $sort) |
auth.yml | JWT validation pattern (optional) |
seed.yml | Clears the collections and loads sample data โ no migrations |
There is no schema.sql. MongoDB is schemaless: collections are created on first insert, and the flexible document shape (note the nested profile sub-document on users and the tags array on posts) is the point.
Endpointsโ
Usersโ
| Method | Path | Body | Description |
|---|---|---|---|
GET | /api/users | โ | List users (passwordHash excluded) |
POST | /api/users/get | {username} | Get user by username |
POST | /api/users/create | {username, email, password} | Create user |
PUT | /api/users/update | {username, email, isActive} | Update user |
DELETE | /api/users/delete | {username} | Delete user |
Postsโ
| Method | Path | Body | Description |
|---|---|---|---|
GET | /api/posts | โ | List published posts |
GET | /api/posts/all | โ | List all posts |
POST | /api/posts/get | {slug} | Get post by slug |
POST | /api/posts/create | {slug, title, body, status, author, category} | Create post |
PUT | /api/posts/update | {slug, title, body, status} | Update post |
DELETE | /api/posts/delete | {slug} | Delete post |
Categoriesโ
| Method | Path | Body | Description |
|---|---|---|---|
GET | /api/categories | โ | List categories |
POST | /api/categories/get | {slug} | Get category by slug |
POST | /api/categories/create | {slug, name, description} | Create category |
DELETE | /api/categories/delete | {slug} | Delete category |
Analytics (MongoDB aggregation)โ
| Method | Path | Description |
|---|---|---|
GET | /api/analytics/posts-by-status | Post counts grouped by status |
GET | /api/analytics/posts-by-category | Published-post count + avg rating per category |
GET | /api/analytics/popular-tags | Top 10 tags ($unwind the tags array) |
GET | /api/analytics/author-activity | Post count + avg rating per author |
Auth (optional) & Seedโ
| Method | Path | Body | Description |
|---|---|---|---|
POST | /api/me | {token} | JWT-protected current-user fetch |
POST | /api/seed | โ | Clear collections and load sample data |
Setupโ
1. Create a free MongoDB Atlas clusterโ
- Sign up at mongodb.com/cloud/atlas and create a free M0 cluster (no credit card).
- Under Database Access, create a database user with a password.
- Under Network Access, allow the IP of wherever AirPipe runs. For hosted AirPipe, whitelist its published egress ranges from ip-ranges.airpipe.io rather than opening the database to everything (
0.0.0.0/0is fine only for quick local testing). - Click Connect โ Drivers and copy the SRV connection string:
mongodb+srv://<user>:<pass>@cluster0.xxxxx.mongodb.net/?retryWrites=true&w=majority
This pack uses a database named appdb (the database: field in every config). The database is created automatically on first insert โ change appdb throughout if you prefer a different name.
2. Set the managed variableโ
In the AirPipe dashboard, add a managed variable:
| Name | Value |
|---|---|
MONGODB_URI | your Atlas SRV connection string |
JWT_SECRET | (optional) 32+ character secret, only needed for auth.yml |
3. Deploy the configsโ
Point AirPipe at the config files. Each name: is unique, so all files can be deployed together.
4. Load sample dataโ
curl -X POST https://your-airpipe-host/api/seed
# โ [ { "status": "archived", "count": 1 },
# { "status": "draft", "count": 1 },
# { "status": "published","count": 4 } ]
Quick start: curl walkthroughโ
BASE=https://your-airpipe-host
# Seed sample users, posts, and categories
curl -X POST $BASE/api/seed
# List published posts
curl $BASE/api/posts
# Create a user (password is bcrypt-hashed before storage)
curl -X POST $BASE/api/users/create \
-H "Content-Type: application/json" \
-d '{"username": "jane", "email": "[email protected]", "password": "Secret1!"}'
# Create a post
curl -X POST $BASE/api/posts/create \
-H "Content-Type: application/json" \
-d '{"slug":"hello-mongo","title":"Hello Mongo","body":"First post.","status":"published","author":"jane","category":"tutorials"}'
# The aggregation payoff โ analytics straight from the database
curl $BASE/api/analytics/posts-by-category
curl $BASE/api/analytics/popular-tags
Why this is a MongoDB pack, not "the Postgres starter with a different driver"โ
The CRUD endpoints look familiar on purpose โ they're the companion to the Postgres REST API Starter pack. But two things here are MongoDB's to show off:
- No migrations, flexible documents. Users carry a nested
profilesub-document and arolesarray; posts carry atagsarray. There's noALTER TABLEโ add a field by just writing it. - Aggregation pipelines as endpoints.
analytics.ymlruns real$group/$unwind/$avg/$sortpipelines inside the database and serves them as plain GET routes. This is the thing a workflow tool (Zapier, n8n) fundamentally can't do.
JWT validation (optional)โ
AirPipe validates JWTs but does not generate them. To protect an endpoint:
Generate a test token at jwt.io:
- Algorithm:
HS256 - Payload:
{"sub": "alice", "username": "alice", "exp": 9999999999}โsubis the username this pack looks users up by - Secret: the value of
JWT_SECRET
Add the JWT_SECRET managed variable, deploy auth.yml, then:
curl -X POST $BASE/api/me \
-H "Content-Type: application/json" \
-d '{"token": "<your-jwt-here>"}'
To protect any other endpoint, copy the ValidateToken action from auth.yml as the first action in the interface and add run_when_succeeded: { actions: [ValidateToken], http_code_on_error: 401 } to every subsequent action.
Notes & customisationโ
- Document keys. Lookups use human-friendly unique keys โ
usernamefor users,slugfor posts and categories โ rather than the raw_idObjectId, which keeps the API readable and avoids ObjectId casting. - Password hashing.
users/createruns abcryptpost-transform (cost 12) before the insert; the raw password is never stored, and every user-returning endpoint stripspasswordHash(via$projecton lists,projectionon single fetches). - Update semantics. MongoDB
$setoverwrites the fields you send, so the update endpoints require those fields rather than doing a partial/COALESCE-style merge. Send the full set of fields you want to change. - Timestamps. Created documents get a
createdAtvia thea|timestamp:datetimeutctz|directive. List endpoints sort by_iddescending (ObjectIds are time-ordered), so newest-first works without depending oncreatedAt. - Reference integrity.
authorandcategoryon a post are plain strings; MongoDB won't enforce that they point at a real user/category. Keeping them consistent is the application's job โ a deliberate trade for schema flexibility. - Use a different database name. Change
appdb(thedatabase:field) across the configs to point at another Atlas database.
Configurationโ
analytics.ymlโ
name: MongoAtlasAnalytics
docs: true
# Analytics endpoints โ MongoDB aggregation pipelines.
#
# This is the part a workflow tool can't replicate: real $group / $unwind /
# $avg / $sort pipelines running inside the database, exposed as plain GET
# endpoints. Each interface is a single aggregate action.
#
# Required managed variable:
# MONGODB_URI โ MongoDB Atlas SRV connection string
global:
databases:
mongo:
driver: mongodb
conn_string: "a|ap_var::MONGODB_URI|"
interfaces:
# GET /api/analytics/posts-by-status
# Count posts grouped by status (draft / published / archived).
api/analytics/posts-by-status:
output: http
summary: Posts by status
description: Counts posts grouped by status, sorted alphabetically.
tags: [analytics]
response_example:
- status: archived
count: 1
- status: draft
count: 1
- status: published
count: 4
actions:
- name: PostsByStatus
database: mongo
document_operation:
database: appdb
collection: posts
operation: aggregate
pipeline: |
[
{ "$group": { "_id": "$status", "count": { "$sum": 1 } } },
{ "$sort": { "_id": 1 } },
{ "$project": { "_id": 0, "status": "$_id", "count": 1 } }
]
# GET /api/analytics/posts-by-category
# For published posts: count and average rating per category, busiest first.
api/analytics/posts-by-category:
output: http
summary: Posts by category
description: >
Aggregates published posts by category, returning the post count and
average rating for each, sorted by post count descending.
tags: [analytics]
response_example:
- category: guides
posts: 2
avgRating: 4.45
- category: tutorials
posts: 2
avgRating: 4.85
actions:
- name: PostsByCategory
database: mongo
document_operation:
database: appdb
collection: posts
operation: aggregate
pipeline: |
[
{ "$match": { "status": "published" } },
{ "$group": {
"_id": "$category",
"posts": { "$sum": 1 },
"avgRating": { "$avg": "$rating" }
} },
{ "$sort": { "posts": -1, "_id": 1 } },
{ "$project": {
"_id": 0,
"category": "$_id",
"posts": 1,
"avgRating": { "$round": ["$avgRating", 2] }
} }
]
# GET /api/analytics/popular-tags
# Flatten the tags arrays and rank the 10 most-used tags.
api/analytics/popular-tags:
output: http
summary: Popular tags
description: Unwinds post tags and returns the 10 most frequent, most popular first.
tags: [analytics]
response_example:
- tag: mongodb
count: 3
- tag: airpipe
count: 2
actions:
- name: PopularTags
database: mongo
document_operation:
database: appdb
collection: posts
operation: aggregate
pipeline: |
[
{ "$unwind": "$tags" },
{ "$group": { "_id": "$tags", "count": { "$sum": 1 } } },
{ "$sort": { "count": -1, "_id": 1 } },
{ "$limit": 10 },
{ "$project": { "_id": 0, "tag": "$_id", "count": 1 } }
]
# GET /api/analytics/author-activity
# Posts and average rating per author, most prolific first.
api/analytics/author-activity:
output: http
summary: Author activity
description: >
Aggregates all posts by author, returning post count and average rating
per author, sorted by post count descending.
tags: [analytics]
response_example:
- author: alice
posts: 2
avgRating: 4.85
- author: bob
posts: 2
avgRating: 2.3
actions:
- name: AuthorActivity
database: mongo
document_operation:
database: appdb
collection: posts
operation: aggregate
pipeline: |
[
{ "$group": {
"_id": "$author",
"posts": { "$sum": 1 },
"avgRating": { "$avg": "$rating" }
} },
{ "$sort": { "posts": -1, "_id": 1 } },
{ "$project": {
"_id": 0,
"author": "$_id",
"posts": 1,
"avgRating": { "$round": ["$avgRating", 2] }
} }
]
auth.ymlโ
name: MongoAtlasAuth
docs: true
# JWT validation middleware pattern (MongoDB edition).
#
# AirPipe validates JWTs but does not generate them. To get a token for testing:
# 1. Go to https://jwt.io
# 2. Set algorithm to HS256
# 3. Set payload: { "sub": "alice", "username": "alice", "exp": 9999999999 }
# (here `sub` is the user's username โ the key this pack looks users up by)
# 4. Set the secret to match JWT_SECRET below
# 5. Copy the encoded token
#
# Then pass the token as { "token": "<jwt>" } in the request body.
#
# Required managed variables:
# MONGODB_URI โ MongoDB Atlas SRV connection string
# JWT_SECRET โ 32+ character secret used to sign tokens
global:
databases:
mongo:
driver: mongodb
conn_string: "a|ap_var::MONGODB_URI|"
interfaces:
# POST /api/me
# JWT-protected current-user fetch.
# Body: { "token": "<jwt>" }
#
# To protect any other endpoint: copy the ValidateToken action and add
# run_when_succeeded: { actions: [ValidateToken], http_code_on_error: 401 }
# to every subsequent action.
api/me:
output: http
method: POST
summary: Get current user
description: >
JWT-protected endpoint. Validates the token, reads the `sub` claim as a
username, and returns that user's profile (without passwordHash).
tags: [auth]
request_example:
token: "<your-jwt-here>"
response_example:
username: alice
email: [email protected]
isActive: true
roles: [admin, member]
actions:
- name: ValidateToken
input: a|body|
hide_data_on_success: true
assert:
http_code_on_error: 401
error_message: "Invalid or missing token"
tests:
- value: token
is_not_null: true
is_valid_jwt: a|ap_var::JWT_SECRET|
post_transforms:
- extract_value: jwt_claims
- name: GetCurrentUser
run_when_succeeded:
actions: [ValidateToken]
http_code_on_error: 401
database: mongo
document_operation:
database: appdb
collection: users
operation: findOne
filter:
username: "a|ValidateToken::sub|"
options:
projection:
passwordHash: 0
assert:
http_code_on_error: 404
error_message: "User not found"
tests:
- value: username
is_not_null: true
categories.ymlโ
name: MongoAtlasCategories
docs: true
# Categories collection โ list / get / create / delete over MongoDB.
# Documents are keyed by a unique `slug`. Posts reference categories by this slug.
#
# Required managed variable:
# MONGODB_URI โ MongoDB Atlas SRV connection string
global:
databases:
mongo:
driver: mongodb
conn_string: "a|ap_var::MONGODB_URI|"
interfaces:
# GET /api/categories
# List all categories, alphabetically by name.
api/categories:
output: http
summary: List categories
description: Returns all categories ordered by name.
tags: [categories]
response_example:
- slug: tutorials
name: Tutorials
description: Step-by-step walkthroughs.
postCount: 2
actions:
- name: ListCategories
database: mongo
document_operation:
database: appdb
collection: categories
operation: find
filter: {}
options:
sort:
name: 1
limit: 100
# POST /api/categories/get
# Fetch a single category by slug.
# Body: { "slug": "tutorials" }
api/categories/get:
output: http
method: POST
summary: Get category
description: Fetch a single category by slug. Returns 404 if not found.
tags: [categories]
request_example:
slug: tutorials
response_example:
slug: tutorials
name: Tutorials
description: Step-by-step walkthroughs.
postCount: 2
actions:
- name: ValidateBody
input: a|body|
hide_data_on_success: true
assert:
tests:
- value: slug
is_not_null: true
is_not_empty: true
- name: GetCategory
run_when_succeeded:
actions: [ValidateBody]
http_code_on_error: 400
database: mongo
document_operation:
database: appdb
collection: categories
operation: findOne
filter:
slug: "a|ValidateBody::slug|"
assert:
http_code_on_error: 404
error_message: "Category not found"
tests:
- value: slug
is_not_null: true
# POST /api/categories/create
# Create a new category.
# Body: { "slug": "...", "name": "...", "description": "..." }
api/categories/create:
output: http
method: POST
summary: Create category
description: Create a new category. `postCount` starts at 0.
tags: [categories]
request_example:
slug: case-studies
name: Case Studies
description: Real-world AirPipe builds.
response_example:
slug: case-studies
name: Case Studies
description: Real-world AirPipe builds.
postCount: 0
actions:
- name: ValidateBody
input: a|body|
hide_data_on_success: true
assert:
tests:
- value: slug
is_not_null: true
is_not_empty: true
- value: name
is_not_null: true
is_not_empty: true
- value: description
is_not_null: true
is_not_empty: true
- name: CreateCategory
run_when_succeeded:
actions: [ValidateBody]
http_code_on_error: 400
database: mongo
hide_data_on_success: true
document_operation:
database: appdb
collection: categories
operation: insertOne
insert:
slug: "a|ValidateBody::slug|"
name: "a|ValidateBody::name|"
description: "a|ValidateBody::description|"
postCount: 0
createdAt: "a|timestamp:datetimeutctz|"
- name: FetchCreated
run_when_succeeded:
actions: [CreateCategory]
http_code_on_error: 500
database: mongo
document_operation:
database: appdb
collection: categories
operation: findOne
filter:
slug: "a|ValidateBody::slug|"
# DELETE /api/categories/delete
# Delete a category by slug.
# Body: { "slug": "case-studies" }
api/categories/delete:
output: http
method: DELETE
summary: Delete category
description: Permanently delete a category by slug. Returns 404 if no document matched.
tags: [categories]
request_example:
slug: case-studies
response_example:
deleted: 1
actions:
- name: ValidateBody
input: a|body|
hide_data_on_success: true
assert:
tests:
- value: slug
is_not_null: true
is_not_empty: true
- name: DeleteCategory
run_when_succeeded:
actions: [ValidateBody]
http_code_on_error: 400
database: mongo
document_operation:
database: appdb
collection: categories
operation: deleteOne
delete:
slug: "a|ValidateBody::slug|"
assert:
http_code_on_error: 404
error_message: "Category not found"
tests:
- value: deleted
is_equal_to: 1
posts.ymlโ
name: MongoAtlasPosts
docs: true
# Posts collection โ CRUD over MongoDB.
#
# Documents are keyed by a unique `slug`. Each post references an `author`
# (a username) and a `category` (a category slug), and carries a flexible
# `tags` array and a numeric `rating` โ the shape that makes the aggregation
# endpoints in analytics.yml interesting.
#
# Required managed variable:
# MONGODB_URI โ MongoDB Atlas SRV connection string
global:
databases:
mongo:
driver: mongodb
conn_string: "a|ap_var::MONGODB_URI|"
interfaces:
# GET /api/posts
# List published posts, newest first.
api/posts:
output: http
summary: List published posts
description: Returns the 100 most recent published posts.
tags: [posts]
response_example:
- slug: mongodb-rest-api-in-minutes
title: A MongoDB REST API in Minutes
status: published
author: alice
category: tutorials
rating: 4.9
actions:
- name: ListPosts
database: mongo
document_operation:
database: appdb
collection: posts
operation: find
filter:
status: published
options:
sort:
_id: -1
limit: 100
# GET /api/posts/all
# List all posts regardless of status (admin view).
api/posts/all:
output: http
summary: List all posts
description: Returns all posts regardless of status. Useful for admin views.
tags: [posts]
response_example:
- slug: launch-week-recap
title: Launch Week Recap
status: draft
author: bob
category: announcements
actions:
- name: ListAllPosts
database: mongo
document_operation:
database: appdb
collection: posts
operation: find
filter: {}
options:
sort:
_id: -1
limit: 100
# POST /api/posts/get
# Fetch a single post by slug.
# Body: { "slug": "mongodb-rest-api-in-minutes" }
api/posts/get:
output: http
method: POST
summary: Get post
description: Fetch a single post by slug. Returns 404 if not found.
tags: [posts]
request_example:
slug: mongodb-rest-api-in-minutes
response_example:
slug: mongodb-rest-api-in-minutes
title: A MongoDB REST API in Minutes
body: "Point AirPipe at your Atlas cluster and you have an API."
status: published
author: alice
category: tutorials
tags: [mongodb, api, atlas]
rating: 4.9
actions:
- name: ValidateBody
input: a|body|
hide_data_on_success: true
assert:
tests:
- value: slug
is_not_null: true
is_not_empty: true
- name: GetPost
run_when_succeeded:
actions: [ValidateBody]
http_code_on_error: 400
database: mongo
document_operation:
database: appdb
collection: posts
operation: findOne
filter:
slug: "a|ValidateBody::slug|"
assert:
http_code_on_error: 404
error_message: "Post not found"
tests:
- value: slug
is_not_null: true
# POST /api/posts/create
# Create a new post.
# Body: { "slug", "title", "body", "status", "author", "category" }
api/posts/create:
output: http
method: POST
summary: Create post
description: >
Create a new post. All six fields (slug, title, body, status, author,
category) are required. `tags` defaults to [] and `rating` to 0 โ set
them later with PUT /api/posts/update or via the tags showcase.
tags: [posts]
notes: |
`slug` must be unique. `author` should be an existing user's username and
`category` an existing category slug, but the API does not enforce the
reference โ MongoDB is schemaless, so consistency is the app's job.
request_example:
slug: my-first-post
title: My First Post
body: "Hello from AirPipe + MongoDB."
status: published
author: alice
category: tutorials
response_example:
slug: my-first-post
title: My First Post
status: published
author: alice
category: tutorials
tags: []
rating: 0
actions:
- name: ValidateBody
input: a|body|
hide_data_on_success: true
assert:
tests:
- value: slug
is_not_null: true
is_not_empty: true
- value: title
is_not_null: true
is_not_empty: true
- value: body
is_not_null: true
is_not_empty: true
- value: status
is_not_null: true
is_not_empty: true
- value: author
is_not_null: true
is_not_empty: true
- value: category
is_not_null: true
is_not_empty: true
- name: CreatePost
run_when_succeeded:
actions: [ValidateBody]
http_code_on_error: 400
database: mongo
hide_data_on_success: true
document_operation:
database: appdb
collection: posts
operation: insertOne
insert:
slug: "a|ValidateBody::slug|"
title: "a|ValidateBody::title|"
body: "a|ValidateBody::body|"
status: "a|ValidateBody::status|"
author: "a|ValidateBody::author|"
category: "a|ValidateBody::category|"
tags: []
rating: 0
createdAt: "a|timestamp:datetimeutctz|"
- name: FetchCreated
run_when_succeeded:
actions: [CreatePost]
http_code_on_error: 500
database: mongo
document_operation:
database: appdb
collection: posts
operation: findOne
filter:
slug: "a|ValidateBody::slug|"
# PUT /api/posts/update
# Update a post's title, body, and status.
# Body: { "slug", "title", "body", "status" }
api/posts/update:
output: http
method: PUT
summary: Update post
description: >
Update a post's title, body, and status. `slug` identifies the document;
send title, body, and status with the values you want to set.
tags: [posts]
request_example:
slug: my-first-post
title: My First Post (edited)
body: "Updated body."
status: published
response_example:
slug: my-first-post
title: My First Post (edited)
status: published
actions:
- name: ValidateBody
input: a|body|
hide_data_on_success: true
assert:
tests:
- value: slug
is_not_null: true
is_not_empty: true
- value: title
is_not_null: true
is_not_empty: true
- value: body
is_not_null: true
is_not_empty: true
- value: status
is_not_null: true
is_not_empty: true
- name: UpdatePost
run_when_succeeded:
actions: [ValidateBody]
http_code_on_error: 400
database: mongo
hide_data_on_success: true
document_operation:
database: appdb
collection: posts
operation: updateOne
filter:
slug: "a|ValidateBody::slug|"
update:
$set:
title: "a|ValidateBody::title|"
body: "a|ValidateBody::body|"
status: "a|ValidateBody::status|"
updatedAt: "a|timestamp:datetimeutctz|"
assert:
http_code_on_error: 404
error_message: "Post not found"
tests:
- value: matched_count
is_equal_to: 1
- name: FetchUpdated
run_when_succeeded:
actions: [UpdatePost]
http_code_on_error: 500
database: mongo
document_operation:
database: appdb
collection: posts
operation: findOne
filter:
slug: "a|ValidateBody::slug|"
# DELETE /api/posts/delete
# Delete a post by slug.
# Body: { "slug": "my-first-post" }
api/posts/delete:
output: http
method: DELETE
summary: Delete post
description: Permanently delete a post by slug. Returns 404 if no document matched.
tags: [posts]
request_example:
slug: my-first-post
response_example:
deleted: 1
actions:
- name: ValidateBody
input: a|body|
hide_data_on_success: true
assert:
tests:
- value: slug
is_not_null: true
is_not_empty: true
- name: DeletePost
run_when_succeeded:
actions: [ValidateBody]
http_code_on_error: 400
database: mongo
document_operation:
database: appdb
collection: posts
operation: deleteOne
delete:
slug: "a|ValidateBody::slug|"
assert:
http_code_on_error: 404
error_message: "Post not found"
tests:
- value: deleted
is_equal_to: 1
seed.ymlโ
name: MongoAtlasSeed
description: Clears the three collections and loads sample users, posts, and categories. Safe to re-run.
docs: true
# POST /api/seed
# Wipes users/posts/categories (deleteMany {}) and reloads sample data with
# insertMany. No schema or migration step โ MongoDB creates the collections on
# first insert. Safe to re-run.
#
# Required managed variable:
# MONGODB_URI โ MongoDB Atlas SRV connection string
global:
databases:
mongo:
driver: mongodb
conn_string: "a|ap_var::MONGODB_URI|"
interfaces:
api/seed:
output: http
method: POST
summary: Seed the database
description: Clears all three collections and loads sample data. Returns the post-status distribution.
tags: [seed]
actions:
# --- clear existing data ---
- name: ClearUsers
database: mongo
hide_data_on_success: true
document_operation:
database: appdb
collection: users
operation: deleteMany
delete: {}
- name: ClearPosts
run_when_succeeded: [ClearUsers]
database: mongo
hide_data_on_success: true
document_operation:
database: appdb
collection: posts
operation: deleteMany
delete: {}
- name: ClearCategories
run_when_succeeded: [ClearPosts]
database: mongo
hide_data_on_success: true
document_operation:
database: appdb
collection: categories
operation: deleteMany
delete: {}
# --- load users (note the flexible nested profile sub-document) ---
- name: InsertUsers
run_when_succeeded: [ClearCategories]
database: mongo
hide_data_on_success: true
document_operation:
database: appdb
collection: users
operation: insertMany
insert:
- username: alice
email: [email protected]
passwordHash: "$2b$12$seededPlaceholderHashForDemoUseOnly000000000000000000"
isActive: true
roles: [admin, member]
profile:
firstName: Alice
lastName: Anderson
age: 31
createdAt: "2026-01-05T09:00:00Z"
- username: bob
email: [email protected]
passwordHash: "$2b$12$seededPlaceholderHashForDemoUseOnly000000000000000000"
isActive: true
roles: [member]
profile:
firstName: Bob
lastName: Brown
age: 27
createdAt: "2026-01-06T10:30:00Z"
- username: carol
email: [email protected]
passwordHash: "$2b$12$seededPlaceholderHashForDemoUseOnly000000000000000000"
isActive: false
roles: [member]
profile:
firstName: Carol
lastName: Clark
age: 24
createdAt: "2026-01-07T14:15:00Z"
- username: dave
email: [email protected]
passwordHash: "$2b$12$seededPlaceholderHashForDemoUseOnly000000000000000000"
isActive: true
roles: [member]
profile:
firstName: Dave
lastName: Davis
age: 38
createdAt: "2026-01-08T08:45:00Z"
# --- load categories ---
- name: InsertCategories
run_when_succeeded: [InsertUsers]
database: mongo
hide_data_on_success: true
document_operation:
database: appdb
collection: categories
operation: insertMany
insert:
- slug: tutorials
name: Tutorials
description: Step-by-step walkthroughs.
postCount: 2
- slug: guides
name: Guides
description: Deeper conceptual guides.
postCount: 2
- slug: announcements
name: Announcements
description: Product news and releases.
postCount: 1
- slug: showcase
name: Showcase
description: Real-world builds on AirPipe.
postCount: 1
# --- load posts (slug-keyed, with author/category/tags/rating) ---
- name: InsertPosts
run_when_succeeded: [InsertCategories]
database: mongo
hide_data_on_success: true
document_operation:
database: appdb
collection: posts
operation: insertMany
insert:
- slug: getting-started-with-airpipe
title: Getting Started with AirPipe
body: "Build an API from a config file in minutes."
status: published
author: alice
category: tutorials
tags: [airpipe, beginner, api]
rating: 4.8
createdAt: "2026-02-01T09:00:00Z"
- slug: mongodb-rest-api-in-minutes
title: A MongoDB REST API in Minutes
body: "Point AirPipe at your Atlas cluster and you have a backend."
status: published
author: alice
category: tutorials
tags: [mongodb, api, atlas]
rating: 4.9
createdAt: "2026-02-03T11:20:00Z"
- slug: aggregation-pipelines-explained
title: Aggregation Pipelines Explained
body: "$group, $unwind and friends, with runnable examples."
status: published
author: bob
category: guides
tags: [mongodb, aggregation, data]
rating: 4.6
createdAt: "2026-02-05T15:40:00Z"
- slug: why-document-databases
title: Why Document Databases
body: "When a flexible schema beats rigid tables."
status: published
author: carol
category: guides
tags: [mongodb, nosql, architecture]
rating: 4.3
createdAt: "2026-02-08T08:10:00Z"
- slug: launch-week-recap
title: Launch Week Recap
body: "Everything we shipped this week (work in progress)."
status: draft
author: bob
category: announcements
tags: [news]
rating: 0
createdAt: "2026-02-10T17:00:00Z"
- slug: building-a-saas-backend
title: Building a SaaS Backend
body: "An older write-up, kept for reference."
status: archived
author: dave
category: showcase
tags: [saas, backend, airpipe]
rating: 4.1
createdAt: "2026-01-20T12:00:00Z"
# --- confirm load by returning the post-status distribution ---
- name: SeedSummary
run_when_succeeded:
actions: [InsertPosts]
http_code_on_error: 500
database: mongo
document_operation:
database: appdb
collection: posts
operation: aggregate
pipeline: |
[
{ "$group": { "_id": "$status", "count": { "$sum": 1 } } },
{ "$sort": { "_id": 1 } },
{ "$project": { "_id": 0, "status": "$_id", "count": 1 } }
]
assert:
tests:
- value: count()
is_greater_than: 0
users.ymlโ
name: MongoAtlasUsers
docs: true
# Users collection โ CRUD over MongoDB.
#
# Documents are keyed by a unique `username`. Passwords are bcrypt-hashed
# (cost 12) before storage; `passwordHash` is never returned by any endpoint
# (list/get use a $project / projection to strip it).
#
# Required managed variable:
# MONGODB_URI โ MongoDB Atlas SRV connection string
# (e.g. mongodb+srv://user:[email protected])
global:
databases:
mongo:
driver: mongodb
conn_string: "a|ap_var::MONGODB_URI|"
interfaces:
# GET /api/users
# List users (newest first), with passwordHash projected out.
api/users:
output: http
summary: List users
description: Returns the 100 most recently created users. `passwordHash` is excluded.
tags: [users]
response_example:
- username: alice
email: [email protected]
isActive: true
roles: [admin, member]
actions:
- name: ListUsers
database: mongo
document_operation:
database: appdb
collection: users
operation: aggregate
pipeline: |
[
{ "$sort": { "_id": -1 } },
{ "$limit": 100 },
{ "$project": { "passwordHash": 0 } }
]
# POST /api/users/get
# Fetch a single user by username.
# Body: { "username": "alice" }
api/users/get:
output: http
method: POST
summary: Get user
description: Fetch a single user by username. Returns 404 if not found.
tags: [users]
request_example:
username: alice
response_example:
username: alice
email: [email protected]
isActive: true
roles: [admin, member]
profile:
firstName: Alice
lastName: Anderson
actions:
- name: ValidateBody
input: a|body|
hide_data_on_success: true
assert:
tests:
- value: username
is_not_null: true
is_not_empty: true
- name: GetUser
run_when_succeeded:
actions: [ValidateBody]
http_code_on_error: 400
database: mongo
document_operation:
database: appdb
collection: users
operation: findOne
filter:
username: "a|ValidateBody::username|"
options:
projection:
passwordHash: 0
assert:
http_code_on_error: 404
error_message: "User not found"
tests:
- value: username
is_not_null: true
# POST /api/users/create
# Create a new user. Password is bcrypt-hashed before storage.
# Body: { "username": "...", "email": "...", "password": "..." }
api/users/create:
output: http
method: POST
summary: Create user
description: >
Create a new user. The password is bcrypt-hashed (cost 12) before storage โ
the raw value is never persisted or returned. New users default to
isActive=true and roles=["member"].
tags: [users]
request_example:
username: jane
email: [email protected]
password: "Secret1!"
response_example:
username: jane
email: [email protected]
isActive: true
roles: [member]
actions:
- name: ValidateBody
input: a|body|
hide_data_on_success: true
assert:
tests:
- value: username
is_not_null: true
is_not_empty: true
- value: email
is_not_null: true
is_not_empty: true
- value: password
is_not_null: true
is_not_empty: true
- name: HashPassword
run_when_succeeded:
actions: [ValidateBody]
http_code_on_error: 400
input: a|ValidateBody|
hide_data_on_success: true
hide_data_on_error: true
post_transforms:
- bcrypt:
value: password
cost: 12
- name: CreateUser
run_when_succeeded:
actions: [HashPassword]
http_code_on_error: 400
database: mongo
hide_data_on_success: true
document_operation:
database: appdb
collection: users
operation: insertOne
insert:
username: "a|ValidateBody::username|"
email: "a|ValidateBody::email|"
passwordHash: "a|HashPassword::password|"
isActive: true
roles: [member]
profile: {}
createdAt: "a|timestamp:datetimeutctz|"
- name: FetchCreated
run_when_succeeded:
actions: [CreateUser]
http_code_on_error: 500
database: mongo
document_operation:
database: appdb
collection: users
operation: findOne
filter:
username: "a|ValidateBody::username|"
options:
projection:
passwordHash: 0
# PUT /api/users/update
# Update a user's email address.
# Body: { "username": "alice", "email": "[email protected]" }
api/users/update:
output: http
method: PUT
summary: Update user
description: >
Update a user's email address. `username` identifies the document; send
`email` with the new value.
tags: [users]
request_example:
username: alice
email: [email protected]
response_example:
username: alice
email: [email protected]
isActive: true
actions:
- name: ValidateBody
input: a|body|
hide_data_on_success: true
assert:
tests:
- value: username
is_not_null: true
is_not_empty: true
- value: email
is_not_null: true
is_not_empty: true
- name: UpdateUser
run_when_succeeded:
actions: [ValidateBody]
http_code_on_error: 400
database: mongo
hide_data_on_success: true
document_operation:
database: appdb
collection: users
operation: updateOne
filter:
username: "a|ValidateBody::username|"
update:
$set:
email: "a|ValidateBody::email|"
updatedAt: "a|timestamp:datetimeutctz|"
assert:
http_code_on_error: 404
error_message: "User not found"
tests:
- value: matched_count
is_equal_to: 1
- name: FetchUpdated
run_when_succeeded:
actions: [UpdateUser]
http_code_on_error: 500
database: mongo
document_operation:
database: appdb
collection: users
operation: findOne
filter:
username: "a|ValidateBody::username|"
options:
projection:
passwordHash: 0
# DELETE /api/users/delete
# Delete a user by username.
# Body: { "username": "alice" }
api/users/delete:
output: http
method: DELETE
summary: Delete user
description: Permanently delete a user by username. Returns 404 if no document matched.
tags: [users]
request_example:
username: alice
response_example:
deleted: 1
actions:
- name: ValidateBody
input: a|body|
hide_data_on_success: true
assert:
tests:
- value: username
is_not_null: true
is_not_empty: true
- name: DeleteUser
run_when_succeeded:
actions: [ValidateBody]
http_code_on_error: 400
database: mongo
document_operation:
database: appdb
collection: users
operation: deleteOne
delete:
username: "a|ValidateBody::username|"
assert:
http_code_on_error: 404
error_message: "User not found"
tests:
- value: deleted
is_equal_to: 1