@cloudgrid-io/mcp 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/package.json +3 -1
  2. package/src/corpus/capability-map.md +46 -28
  3. package/src/corpus/cloudgrid-yaml.md +569 -0
  4. package/src/corpus/examples/ai-app/index.md +237 -0
  5. package/src/corpus/examples/api-service/index.md +190 -0
  6. package/src/corpus/examples/app-with-data/index.md +24 -19
  7. package/src/corpus/templates/ai-app/README.md +95 -0
  8. package/src/corpus/templates/ai-app/cloudgrid.yaml +20 -0
  9. package/src/corpus/templates/ai-app/index.md +269 -0
  10. package/src/corpus/templates/ai-app/services/web/app/api/chat/route.js +51 -0
  11. package/src/corpus/templates/ai-app/services/web/app/chat.js +60 -0
  12. package/src/corpus/templates/ai-app/services/web/app/layout.js +36 -0
  13. package/src/corpus/templates/ai-app/services/web/app/page.js +25 -0
  14. package/src/corpus/templates/ai-app/services/web/lib/db.js +32 -0
  15. package/src/corpus/templates/ai-app/services/web/package.json +17 -0
  16. package/src/corpus/templates/api-service/README.md +87 -0
  17. package/src/corpus/templates/api-service/cloudgrid.yaml +18 -0
  18. package/src/corpus/templates/api-service/index.md +225 -0
  19. package/src/corpus/templates/api-service/services/api/package.json +13 -0
  20. package/src/corpus/templates/api-service/services/api/src/index.js +132 -0
  21. package/src/corpus/templates/app-with-data/README.md +22 -19
  22. package/src/corpus/templates/app-with-data/cloudgrid.yaml +9 -13
  23. package/src/corpus/templates/app-with-data/index.md +37 -38
  24. package/src/corpus/templates/app-with-data/services/web/lib/db.js +10 -9
  25. package/src/corpus/troubleshooting/persistent-apps.md +6 -6
  26. package/src/corpus/workflows/ai-app.md +136 -0
  27. package/src/corpus/workflows/api-service.md +118 -0
  28. package/src/corpus/workflows/app-with-data.md +21 -18
  29. package/src/tools.js +1 -0
@@ -0,0 +1,225 @@
1
+ # Template: api-service (Node HTTP + Mongo REST API)
2
+
3
+ A minimal but real, deployable backend API. A plain Node `http` server (no web
4
+ UI, no framework) serving a small REST resource (`/items`) backed by the
5
+ grid-shared MongoDB. Data survives refresh and is shared across sessions. This is
6
+ the plain-service sibling of `app-with-data` — use it when the user wants a
7
+ backend/API, not a full web app.
8
+
9
+ **Key rules (all proven by a real end-to-end deploy):**
10
+
11
+ 1. **Service code MUST live under `services/<name>/`, not the repo/template
12
+ root.** `path:` in `cloudgrid.yaml` is the URL mount, NOT the filesystem path.
13
+ The service named `api` → the CLI looks for `services/api/`. Files at the root
14
+ fail with `Error: Service directory not found: …/services/api`.
15
+ 2. **Read the DB connection string LAZILY (inside the getter), never at module
16
+ top level.** The grid injects it as the `DATABASE_MONGODB_URL` environment
17
+ variable (plus the legacy `MONGODB_URL` alias) at dev-time and runtime; the
18
+ service reads `process.env.DATABASE_MONGODB_URL || process.env.MONGODB_URL`
19
+ inside `getClient`. A top-level `const uri = process.env.DATABASE_MONGODB_URL;
20
+ if (!uri) throw` crashes node startup before the grid injects the var. Never
21
+ hardcode a connection string; never commit a secret.
22
+ 3. **Declare the datastore with `needs: { database: true }`.** This is the
23
+ canonical shape — the deployer provisions Mongo and injects
24
+ `DATABASE_MONGODB_URL` (plus the legacy `MONGODB_URL` alias). `requires:` is
25
+ the deprecated v1 alias; don't author new yaml with it, and never set `needs:`
26
+ and `requires:` together (the validator rejects the combination).
27
+ 4. **Listen on `process.env.PORT || 8080`.** The grid injects `PORT`; the `8080`
28
+ default is for local runs.
29
+
30
+ Write these files into the scaffolded app folder — the service code goes under
31
+ `services/api/` — adapt the resource/fields to the user's API, then `grid dev`
32
+ (local) / `grid plug` (deploy, async — poll to a live URL).
33
+
34
+ ## File tree
35
+
36
+ ```
37
+ cloudgrid.yaml # name + services.api (node) + needs: { database: true }
38
+ services/api/package.json # type: module, main src/index.js, mongodb driver only
39
+ services/api/src/index.js # Node http server on PORT||8080; /items GET/POST/DELETE on Mongo
40
+ ```
41
+
42
+ ## cloudgrid.yaml
43
+
44
+ ```yaml
45
+ # Rename this service. The grid injects the DB connection string as an
46
+ # environment variable at runtime — do NOT set it yourself, and never commit a
47
+ # connection string or secret.
48
+ #
49
+ # `needs: { database: true }` is the canonical, recommended shape. The deployer
50
+ # provisions Mongo and injects DATABASE_MONGODB_URL (plus the legacy MONGODB_URL
51
+ # alias). `requires:` is the deprecated v1 alias — don't author new yaml with it,
52
+ # and never set `needs:` and `requires:` together (the validator rejects it).
53
+ #
54
+ # This is a plain Node HTTP service (type: node), not a web UI. Service code lives
55
+ # under services/api/ — `path:` is the URL mount, not the filesystem path.
56
+ name: my-api
57
+ services:
58
+ api:
59
+ type: node
60
+ path: /
61
+ needs:
62
+ database: true
63
+ ```
64
+
65
+ > **Capability:** this template's need is `database: true`. The deployer
66
+ > provisions Mongo and injects `DATABASE_MONGODB_URL` (plus the legacy
67
+ > `MONGODB_URL` alias), so a service reading either var works. `requires:` is the
68
+ > deprecated v1 alias — don't mix it with `needs:` (the validator rejects the
69
+ > combination). See the capability-map for the full injection table.
70
+
71
+ ## services/api/package.json
72
+
73
+ ```json
74
+ {
75
+ "name": "api",
76
+ "version": "0.1.0",
77
+ "private": true,
78
+ "type": "module",
79
+ "main": "src/index.js",
80
+ "scripts": {
81
+ "start": "node src/index.js"
82
+ },
83
+ "dependencies": {
84
+ "mongodb": "^6.12.0"
85
+ }
86
+ }
87
+ ```
88
+
89
+ ## services/api/src/index.js
90
+
91
+ ```js
92
+ // A minimal REST/JSON API backed by grid-shared Mongo.
93
+ //
94
+ // Endpoints (resource: `items`):
95
+ // GET /items -> list items (newest first)
96
+ // POST /items -> create an item; body { "text": string }
97
+ // DELETE /items?id=… -> remove an item by id
98
+ // GET / -> tiny service banner + endpoint list
99
+ //
100
+ // The grid injects DATABASE_MONGODB_URL (plus the legacy MONGODB_URL alias) at
101
+ // dev + runtime. The env var and Mongo client are resolved LAZILY (inside
102
+ // getClient), never at module top level — a top-level read throws at node
103
+ // startup, before the grid injects the var, and crashes the service. Never
104
+ // hardcode a connection string; never commit a secret.
105
+ import { createServer } from "node:http";
106
+ import { MongoClient, ObjectId } from "mongodb";
107
+
108
+ let clientPromise;
109
+
110
+ function getClient() {
111
+ const uri = process.env.DATABASE_MONGODB_URL || process.env.MONGODB_URL;
112
+ if (!uri) {
113
+ throw new Error(
114
+ "DATABASE_MONGODB_URL is not set. The grid injects it automatically — run " +
115
+ "this service with `grid dev` locally, or deploy it with `grid plug`. Do " +
116
+ "not set it by hand.",
117
+ );
118
+ }
119
+ if (!clientPromise) clientPromise = new MongoClient(uri).connect();
120
+ return clientPromise;
121
+ }
122
+
123
+ async function items() {
124
+ const client = await getClient();
125
+ return client.db().collection("items");
126
+ }
127
+
128
+ function sendJson(res, status, body) {
129
+ const payload = JSON.stringify(body);
130
+ res.writeHead(status, {
131
+ "content-type": "application/json",
132
+ "content-length": Buffer.byteLength(payload),
133
+ });
134
+ res.end(payload);
135
+ }
136
+
137
+ function readBody(req) {
138
+ return new Promise((resolve) => {
139
+ let data = "";
140
+ req.on("data", (chunk) => {
141
+ data += chunk;
142
+ if (data.length > 1_000_000) req.destroy();
143
+ });
144
+ req.on("end", () => {
145
+ try {
146
+ resolve(data ? JSON.parse(data) : {});
147
+ } catch {
148
+ resolve(null);
149
+ }
150
+ });
151
+ req.on("error", () => resolve(null));
152
+ });
153
+ }
154
+
155
+ async function handle(req, res) {
156
+ const url = new URL(req.url, "http://localhost");
157
+ const { pathname } = url;
158
+
159
+ if (pathname === "/" && req.method === "GET") {
160
+ return sendJson(res, 200, {
161
+ service: "my-api",
162
+ endpoints: ["GET /items", "POST /items", "DELETE /items?id=<id>"],
163
+ });
164
+ }
165
+
166
+ if (pathname === "/items") {
167
+ const col = await items();
168
+
169
+ if (req.method === "GET") {
170
+ const list = await col.find({}).sort({ createdAt: -1 }).toArray();
171
+ return sendJson(
172
+ res,
173
+ 200,
174
+ list.map((it) => ({ id: it._id.toString(), text: it.text, createdAt: it.createdAt })),
175
+ );
176
+ }
177
+
178
+ if (req.method === "POST") {
179
+ const body = await readBody(req);
180
+ if (body === null) return sendJson(res, 400, { error: "invalid JSON body" });
181
+ const text = typeof body.text === "string" ? body.text.trim() : "";
182
+ if (!text) return sendJson(res, 400, { error: "text is required" });
183
+ const doc = { text, createdAt: new Date() };
184
+ const { insertedId } = await col.insertOne(doc);
185
+ return sendJson(res, 201, { id: insertedId.toString(), ...doc });
186
+ }
187
+
188
+ if (req.method === "DELETE") {
189
+ const id = url.searchParams.get("id");
190
+ if (!id || !ObjectId.isValid(id)) {
191
+ return sendJson(res, 400, { error: "a valid id query param is required" });
192
+ }
193
+ const { deletedCount } = await col.deleteOne({ _id: new ObjectId(id) });
194
+ if (deletedCount === 0) return sendJson(res, 404, { error: "item not found" });
195
+ return sendJson(res, 200, { ok: true });
196
+ }
197
+
198
+ return sendJson(res, 405, { error: "method not allowed" });
199
+ }
200
+
201
+ return sendJson(res, 404, { error: "not found" });
202
+ }
203
+
204
+ const server = createServer((req, res) => {
205
+ handle(req, res).catch((err) => {
206
+ console.error(err);
207
+ if (!res.headersSent) sendJson(res, 500, { error: "internal server error" });
208
+ });
209
+ });
210
+
211
+ const port = process.env.PORT || 8080;
212
+ server.listen(port, () => {
213
+ console.log(`api-service listening on :${port}`);
214
+ });
215
+ ```
216
+
217
+ ## Adapt it
218
+
219
+ - Rename the `items` collection in `services/api/src/index.js` to your resource
220
+ (`notes`, `orders`, `events`).
221
+ - Change the document fields; add validation, owners, timestamps, statuses.
222
+ - Add more routes/resources as the API grows.
223
+ - Return clear JSON errors (`{ "error": … }`) with the right status codes.
224
+ - Add more `needs:` (e.g. `cache: true`) only if you actually use them.
225
+ - Run `grid dev` to test locally, `grid plug` to deploy (async — poll to live).
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "api",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "main": "src/index.js",
7
+ "scripts": {
8
+ "start": "node src/index.js"
9
+ },
10
+ "dependencies": {
11
+ "mongodb": "^6.12.0"
12
+ }
13
+ }
@@ -0,0 +1,132 @@
1
+ // A minimal REST/JSON API backed by grid-shared Mongo.
2
+ //
3
+ // Endpoints (resource: `items`):
4
+ // GET /items -> list items (newest first)
5
+ // POST /items -> create an item; body { "text": string }
6
+ // DELETE /items?id=… -> remove an item by id
7
+ // GET / -> tiny service banner + endpoint list
8
+ //
9
+ // The grid injects the connection string as the DATABASE_MONGODB_URL environment
10
+ // variable (plus the legacy MONGODB_URL alias) at dev-time (`grid dev`) and
11
+ // runtime (after `grid plug`). Read the canonical var first, fall back to the
12
+ // legacy alias.
13
+ //
14
+ // The env var and Mongo client are resolved LAZILY (inside getDb), never at
15
+ // module top level — a top-level read throws at node startup, before the grid
16
+ // has injected the var, and crashes the service. Never hardcode a connection
17
+ // string here and never commit a secret.
18
+ import { createServer } from "node:http";
19
+ import { MongoClient, ObjectId } from "mongodb";
20
+
21
+ let clientPromise;
22
+
23
+ function getClient() {
24
+ const uri = process.env.DATABASE_MONGODB_URL || process.env.MONGODB_URL;
25
+ if (!uri) {
26
+ throw new Error(
27
+ "DATABASE_MONGODB_URL is not set. The grid injects it automatically — run " +
28
+ "this service with `grid dev` locally, or deploy it with `grid plug` (the " +
29
+ "grid injects the DB connection string at runtime). Do not set it by hand.",
30
+ );
31
+ }
32
+ if (!clientPromise) clientPromise = new MongoClient(uri).connect();
33
+ return clientPromise;
34
+ }
35
+
36
+ async function items() {
37
+ const client = await getClient();
38
+ // The default DB comes from the connection-string path segment the grid injects.
39
+ return client.db().collection("items");
40
+ }
41
+
42
+ // ── Small JSON helpers ───────────────────────────────────────────────────────
43
+ function sendJson(res, status, body) {
44
+ const payload = JSON.stringify(body);
45
+ res.writeHead(status, {
46
+ "content-type": "application/json",
47
+ "content-length": Buffer.byteLength(payload),
48
+ });
49
+ res.end(payload);
50
+ }
51
+
52
+ function readBody(req) {
53
+ return new Promise((resolve) => {
54
+ let data = "";
55
+ req.on("data", (chunk) => {
56
+ data += chunk;
57
+ if (data.length > 1_000_000) req.destroy(); // basic guard
58
+ });
59
+ req.on("end", () => {
60
+ try {
61
+ resolve(data ? JSON.parse(data) : {});
62
+ } catch {
63
+ resolve(null); // signal invalid JSON
64
+ }
65
+ });
66
+ req.on("error", () => resolve(null));
67
+ });
68
+ }
69
+
70
+ // ── Router ───────────────────────────────────────────────────────────────────
71
+ async function handle(req, res) {
72
+ const url = new URL(req.url, "http://localhost");
73
+ const { pathname } = url;
74
+
75
+ if (pathname === "/" && req.method === "GET") {
76
+ return sendJson(res, 200, {
77
+ service: "my-api",
78
+ endpoints: ["GET /items", "POST /items", "DELETE /items?id=<id>"],
79
+ });
80
+ }
81
+
82
+ if (pathname === "/items") {
83
+ const col = await items();
84
+
85
+ if (req.method === "GET") {
86
+ const list = await col.find({}).sort({ createdAt: -1 }).toArray();
87
+ return sendJson(
88
+ res,
89
+ 200,
90
+ list.map((it) => ({ id: it._id.toString(), text: it.text, createdAt: it.createdAt })),
91
+ );
92
+ }
93
+
94
+ if (req.method === "POST") {
95
+ const body = await readBody(req);
96
+ if (body === null) return sendJson(res, 400, { error: "invalid JSON body" });
97
+ const text = typeof body.text === "string" ? body.text.trim() : "";
98
+ if (!text) return sendJson(res, 400, { error: "text is required" });
99
+ const doc = { text, createdAt: new Date() };
100
+ const { insertedId } = await col.insertOne(doc);
101
+ return sendJson(res, 201, { id: insertedId.toString(), ...doc });
102
+ }
103
+
104
+ if (req.method === "DELETE") {
105
+ const id = url.searchParams.get("id");
106
+ if (!id || !ObjectId.isValid(id)) {
107
+ return sendJson(res, 400, { error: "a valid id query param is required" });
108
+ }
109
+ const { deletedCount } = await col.deleteOne({ _id: new ObjectId(id) });
110
+ if (deletedCount === 0) return sendJson(res, 404, { error: "item not found" });
111
+ return sendJson(res, 200, { ok: true });
112
+ }
113
+
114
+ return sendJson(res, 405, { error: "method not allowed" });
115
+ }
116
+
117
+ return sendJson(res, 404, { error: "not found" });
118
+ }
119
+
120
+ const server = createServer((req, res) => {
121
+ handle(req, res).catch((err) => {
122
+ // Never leak internals or a connection string in the response.
123
+ console.error(err);
124
+ if (!res.headersSent) sendJson(res, 500, { error: "internal server error" });
125
+ });
126
+ });
127
+
128
+ // The grid injects PORT; default to 8080 for local runs.
129
+ const port = process.env.PORT || 8080;
130
+ server.listen(port, () => {
131
+ console.log(`api-service listening on :${port}`);
132
+ });
@@ -7,23 +7,26 @@ page whose state is in memory only.
7
7
  ## How the grid gives you a database
8
8
 
9
9
  You do **not** provision a database or set a connection string. In
10
- `cloudgrid.yaml` you declare `requires: [mongodb]`, and the grid:
10
+ `cloudgrid.yaml` you declare `needs: { database: true }`, and the grid:
11
11
 
12
12
  - provisions shared Mongo for the app, and
13
- - injects the connection string as the **`MONGODB_URL`** environment variable —
14
- at dev-time (`grid dev`) and at runtime (after `grid plug`).
15
-
16
- The app reads it via `process.env.MONGODB_URL` in `services/web/lib/db.js` —
17
- **lazily, inside the `getDb` getter, never at module top level** (a top-level
18
- read fails `next build`, which imports the module for route analysis before the
19
- grid injects the var). Never hardcode a connection string; never commit a
20
- secret. (If you also declare `redis: private`, the grid injects `REDIS_URL` the
21
- same way.)
22
-
23
- > **Use `requires:` (NOT `needs:`).** The CLI warns `requires:` is deprecated,
24
- > but the deployer currently only injects `MONGODB_URL` from `requires:` —
25
- > migrating to `needs:` builds fine but injects NO DB connection (every request
26
- > 500s). Keep `requires:` until CloudGrid's deployer honors `needs:`.
13
+ - injects the connection string as the **`DATABASE_MONGODB_URL`** environment
14
+ variable (plus the legacy `MONGODB_URL` alias) — at dev-time (`grid dev`) and
15
+ at runtime (after `grid plug`).
16
+
17
+ The app reads it via `process.env.DATABASE_MONGODB_URL` (with a legacy
18
+ `process.env.MONGODB_URL` fallback) in `services/web/lib/db.js` — **lazily,
19
+ inside the `getDb` getter, never at module top level** (a top-level read fails
20
+ `next build`, which imports the module for route analysis before the grid injects
21
+ the var). Never hardcode a connection string; never commit a secret. (If you also
22
+ declare `cache: true`, the grid injects `CACHE_REDIS_URL` — and the legacy
23
+ `REDIS_URL` alias — the same way.)
24
+
25
+ > **Declare `needs: { database: true }` (the canonical shape).** The deployer
26
+ > provisions Mongo and injects `DATABASE_MONGODB_URL` (plus the legacy
27
+ > `MONGODB_URL` alias). `requires:` is the deprecated v1 alias — don't author new
28
+ > yaml with it, and never set `needs:` and `requires:` together (the validator
29
+ > rejects the combination).
27
30
 
28
31
  ## Service layout
29
32
 
@@ -36,7 +39,7 @@ App code lives under **`services/web/`**, not the template root. `path:` in
36
39
 
37
40
  ```bash
38
41
  npm install
39
- grid dev # runs Next.js with MONGODB_URL injected against dev Mongo
42
+ grid dev # runs Next.js with DATABASE_MONGODB_URL injected against dev Mongo
40
43
  ```
41
44
 
42
45
  ## Deploy
@@ -52,9 +55,9 @@ to update the same URL.
52
55
  ## File tree
53
56
 
54
57
  ```
55
- cloudgrid.yaml # name + services.web (nextjs) + requires: [mongodb]
58
+ cloudgrid.yaml # name + services.web (nextjs) + needs: { database: true }
56
59
  services/web/package.json # next, react, react-dom, mongodb driver only
57
- services/web/lib/db.js # lazy Mongo client from process.env.MONGODB_URL
60
+ services/web/lib/db.js # lazy Mongo client from DATABASE_MONGODB_URL (legacy MONGODB_URL fallback)
58
61
  services/web/app/layout.js # root layout + inline CSS
59
62
  services/web/app/page.js # server component: reads todos from Mongo
60
63
  services/web/app/todo-form.js # client form: POST/DELETE via the API
@@ -67,4 +70,4 @@ services/web/app/api/todos/route.js # GET (list) / POST (add) / DELETE (remove
67
70
  `services/web/app/page.js` (e.g. `submissions`, `tasks`, `entries`).
68
71
  - Change the document fields (add owners, timestamps, statuses).
69
72
  - Add more routes/collections as the app grows.
70
- - Add `redis: private` to `requires` only if you actually need Redis.
73
+ - Add `cache: true` to `needs:` only if you actually need Redis.
@@ -1,19 +1,15 @@
1
- # Rename this app. The grid injects MONGODB_URL (and REDIS_URL if you add a cache)
2
- # as environment variables at runtime — do NOT set them yourself, and never commit
3
- # a connection string or secret.
1
+ # Rename this app. The grid injects the DB connection string (and a Redis URL if
2
+ # you add a cache) as environment variables at runtime — do NOT set them
3
+ # yourself, and never commit a connection string or secret.
4
4
  #
5
- # NOTE: use `requires:` (NOT `needs:`). The CLI warns that `requires:` is deprecated,
6
- # but the deployer currently only injects MONGODB_URL from `requires:` — migrating to
7
- # `needs:` builds fine but injects NO database connection (every request 500s).
8
- # Keep `requires:` until CloudGrid's deployer honors `needs:`.
5
+ # `needs: { database: true }` is the canonical, recommended shape. The deployer
6
+ # provisions Mongo and injects DATABASE_MONGODB_URL (plus the legacy MONGODB_URL
7
+ # alias). `requires:` is the deprecated v1 alias — don't author new yaml with it,
8
+ # and never set `needs:` and `requires:` together (the validator rejects it).
9
9
  name: my-app
10
10
  services:
11
11
  web:
12
12
  type: nextjs
13
13
  path: /
14
- # Canonical capability (metadata): this app needs a database.
15
- # When #1527 lands (deployer honors needs:), replace requires: with:
16
- # needs:
17
- # database: true
18
- requires:
19
- - mongodb
14
+ needs:
15
+ database: true
@@ -9,17 +9,19 @@ so it survives refresh and is shared across sessions — unlike a static page.
9
9
  root.** `path:` in `cloudgrid.yaml` is the URL mount, NOT the filesystem path.
10
10
  The service named `web` → the CLI looks for `services/web/`. App files at the
11
11
  root fail with `Error: Service directory not found: …/services/web`.
12
- 2. **Read `MONGODB_URL` LAZILY (inside the getter), never at module top level.**
13
- The grid injects the DB connection string as the `MONGODB_URL` environment
14
- variable at dev-time and runtime; the app reads `process.env.MONGODB_URL`
15
- inside `getDb`. A top-level `const uri = process.env.MONGODB_URL; if (!uri)
12
+ 2. **Read the DB connection string LAZILY (inside the getter), never at module
13
+ top level.** The grid injects it as the `DATABASE_MONGODB_URL` environment
14
+ variable (plus the legacy `MONGODB_URL` alias) at dev-time and runtime; the
15
+ app reads `process.env.DATABASE_MONGODB_URL || process.env.MONGODB_URL` inside
16
+ `getDb`. A top-level `const uri = process.env.DATABASE_MONGODB_URL; if (!uri)
16
17
  throw` fails `next build` (the module is imported for route analysis before
17
18
  the grid injects the var). Never hardcode a connection string; never commit a
18
19
  secret.
19
- 3. **Declare the datastore with `requires: [mongodb]` — NOT `needs:`.** The CLI
20
- warns `requires:` is deprecated, but the deployer only injects `MONGODB_URL`
21
- from `requires:`; `needs:` builds fine but injects NO DB connection (every
22
- request 500s). Keep `requires:` until the deployer honors `needs:`.
20
+ 3. **Declare the datastore with `needs: { database: true }`.** This is the
21
+ canonical shape — the deployer provisions Mongo and injects
22
+ `DATABASE_MONGODB_URL` (plus the legacy `MONGODB_URL` alias). `requires:` is
23
+ the deprecated v1 alias; don't author new yaml with it, and never set `needs:`
24
+ and `requires:` together (the validator rejects the combination).
23
25
 
24
26
  Write these files into the scaffolded app folder — the app code goes under
25
27
  `services/web/` — adapt the collection/fields to the user's app, then `grid dev`
@@ -28,9 +30,9 @@ Write these files into the scaffolded app folder — the app code goes under
28
30
  ## File tree
29
31
 
30
32
  ```
31
- cloudgrid.yaml # name + services.web (nextjs) + requires: [mongodb]
33
+ cloudgrid.yaml # name + services.web (nextjs) + needs: { database: true }
32
34
  services/web/package.json # next, react, react-dom, mongodb driver only
33
- services/web/lib/db.js # lazy Mongo client from process.env.MONGODB_URL
35
+ services/web/lib/db.js # lazy Mongo client from DATABASE_MONGODB_URL (legacy MONGODB_URL fallback)
34
36
  services/web/app/layout.js # root layout + inline CSS
35
37
  services/web/app/page.js # server component: reads todos from Mongo
36
38
  services/web/app/todo-form.js # client form: POST/DELETE via the API
@@ -40,32 +42,28 @@ services/web/app/api/todos/route.js # GET (list) / POST (add) / DELETE (remo
40
42
  ## cloudgrid.yaml
41
43
 
42
44
  ```yaml
43
- # Rename this app. The grid injects MONGODB_URL (and REDIS_URL if you add a cache)
44
- # as environment variables at runtime — do NOT set them yourself, and never commit
45
- # a connection string or secret.
45
+ # Rename this app. The grid injects the DB connection string (and a Redis URL if
46
+ # you add a cache) as environment variables at runtime — do NOT set them
47
+ # yourself, and never commit a connection string or secret.
46
48
  #
47
- # NOTE: use `requires:` (NOT `needs:`). The CLI warns that `requires:` is deprecated,
48
- # but the deployer currently only injects MONGODB_URL from `requires:` — migrating to
49
- # `needs:` builds fine but injects NO database connection (every request 500s).
50
- # Keep `requires:` until CloudGrid's deployer honors `needs:`.
49
+ # `needs: { database: true }` is the canonical, recommended shape. The deployer
50
+ # provisions Mongo and injects DATABASE_MONGODB_URL (plus the legacy MONGODB_URL
51
+ # alias). `requires:` is the deprecated v1 alias — don't author new yaml with it,
52
+ # and never set `needs:` and `requires:` together (the validator rejects it).
51
53
  name: my-app
52
54
  services:
53
55
  web:
54
56
  type: nextjs
55
57
  path: /
56
- # Canonical capability (metadata): this app needs a database.
57
- # When #1527 lands (deployer honors needs:), replace requires: with:
58
- # needs:
59
- # database: true
60
- requires:
61
- - mongodb
58
+ needs:
59
+ database: true
62
60
  ```
63
61
 
64
- > **Capability:** this template's canonical need is `database: true` (see the
65
- > commented block above and the capability-map). The deployed yaml keeps
66
- > `requires: [mongodb]` because only `requires:` injects `MONGODB_URL` today
67
- > (#1527); `needs:` and `requires:` cannot co-exist active in the same yaml, so
68
- > the canonical `needs:` is shown only as a comment until the deployer honors it.
62
+ > **Capability:** this template's need is `database: true`. The deployer
63
+ > provisions Mongo and injects `DATABASE_MONGODB_URL` (plus the legacy
64
+ > `MONGODB_URL` alias), so an app reading either var works. `requires:` is the
65
+ > deprecated v1 alias — don't mix it with `needs:` (the validator rejects the
66
+ > combination). See the capability-map for the full injection table.
69
67
 
70
68
  ## services/web/package.json
71
69
 
@@ -93,22 +91,23 @@ requires:
93
91
  ```js
94
92
  // Cached MongoDB client for the App Router.
95
93
  //
96
- // The grid injects the connection string as the MONGODB_URL environment variable
97
- // at runtime (after `grid plug`) and under `grid dev` locally. Never hardcode a
98
- // connection string here and never commit a secret.
94
+ // The grid injects the connection string as the DATABASE_MONGODB_URL environment
95
+ // variable (plus the legacy MONGODB_URL alias) at runtime (after `grid plug`) and
96
+ // under `grid dev` locally. Read the canonical var first, fall back to the legacy
97
+ // alias. Never hardcode a connection string here and never commit a secret.
99
98
  //
100
99
  // The env var and client are resolved LAZILY (inside getDb), never at module top
101
100
  // level — otherwise `next build` throws when it imports this module for route
102
- // analysis, before the grid injects MONGODB_URL.
101
+ // analysis, before the grid injects the var.
103
102
  import { MongoClient } from "mongodb";
104
103
 
105
104
  function clientPromise() {
106
- const uri = process.env.MONGODB_URL;
105
+ const uri = process.env.DATABASE_MONGODB_URL || process.env.MONGODB_URL;
107
106
  if (!uri) {
108
107
  throw new Error(
109
- "MONGODB_URL is not set. The grid injects it automatically — run this app " +
110
- "with `grid dev` locally, or deploy it with `grid plug` (the grid injects " +
111
- "MONGODB_URL at runtime). Do not set it by hand.",
108
+ "DATABASE_MONGODB_URL is not set. The grid injects it automatically — run " +
109
+ "this app with `grid dev` locally, or deploy it with `grid plug` (the grid " +
110
+ "injects the DB connection string at runtime). Do not set it by hand.",
112
111
  );
113
112
  }
114
113
  if (!globalThis.__mongoClientPromise) {
@@ -119,7 +118,7 @@ function clientPromise() {
119
118
 
120
119
  export async function getDb() {
121
120
  const client = await clientPromise();
122
- // The default DB comes from the MONGODB_URL path segment the grid injects.
121
+ // The default DB comes from the connection-string path segment the grid injects.
123
122
  return client.db();
124
123
  }
125
124
  ```
@@ -267,5 +266,5 @@ export async function DELETE(request) {
267
266
 
268
267
  - Rename the `todos` collection to your data (`submissions`, `tasks`, `entries`).
269
268
  - Change the document fields; add owners/timestamps/statuses.
270
- - Add `redis: private` to `requires` only if you actually need Redis.
269
+ - Add `cache: true` to `needs:` only if you actually need Redis.
271
270
  - Run `grid dev` to test locally, `grid plug` to deploy (async — poll to live).
@@ -1,21 +1,22 @@
1
1
  // Cached MongoDB client for the App Router.
2
2
  //
3
- // The grid injects the connection string as the MONGODB_URL environment variable
4
- // at runtime (after `grid plug`) and under `grid dev` locally. Never hardcode a
5
- // connection string here and never commit a secret.
3
+ // The grid injects the connection string as the DATABASE_MONGODB_URL environment
4
+ // variable (plus the legacy MONGODB_URL alias) at dev-time (`grid dev`) and
5
+ // runtime (after `grid plug`). Read the canonical var first, fall back to the
6
+ // legacy alias. Never hardcode a connection string here and never commit a secret.
6
7
  //
7
8
  // The env var and client are resolved LAZILY (inside getDb), never at module top
8
9
  // level — otherwise `next build` throws when it imports this module for route
9
- // analysis, before the grid injects MONGODB_URL.
10
+ // analysis, before the grid injects the var.
10
11
  import { MongoClient } from "mongodb";
11
12
 
12
13
  function clientPromise() {
13
- const uri = process.env.MONGODB_URL;
14
+ const uri = process.env.DATABASE_MONGODB_URL || process.env.MONGODB_URL;
14
15
  if (!uri) {
15
16
  throw new Error(
16
- "MONGODB_URL is not set. The grid injects it automatically — run this app " +
17
- "with `grid dev` locally, or deploy it with `grid plug` (the grid injects " +
18
- "MONGODB_URL at runtime). Do not set it by hand.",
17
+ "DATABASE_MONGODB_URL is not set. The grid injects it automatically — run " +
18
+ "this app with `grid dev` locally, or deploy it with `grid plug` (the grid " +
19
+ "injects the DB connection string at runtime). Do not set it by hand.",
19
20
  );
20
21
  }
21
22
  if (!globalThis.__mongoClientPromise) {
@@ -26,6 +27,6 @@ function clientPromise() {
26
27
 
27
28
  export async function getDb() {
28
29
  const client = await clientPromise();
29
- // The default DB comes from the MONGODB_URL path segment the grid injects.
30
+ // The default DB comes from the connection-string path segment the grid injects.
30
31
  return client.db();
31
32
  }