Skip to content
This repository was archived by the owner on May 20, 2025. It is now read-only.

Commit 11a0223

Browse files
committed
docs: add survey guide
1 parent 443693f commit 11a0223

File tree

4 files changed

+271
-0
lines changed

4 files changed

+271
-0
lines changed
Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
---
2+
description: Build a serverless backend for capturing and delivering survey submissions using Nitric and TypeScript.
3+
title_seo: Building a Survey Backend with Nitric and TypeScript
4+
tags:
5+
- API
6+
- Event
7+
- PDF
8+
languages:
9+
- typescript
10+
- javascript
11+
published_at: 2025-04-03
12+
updated_at: 2025-04-03
13+
---
14+
15+
# Building a Survey Backend with Nitric and TypeScript
16+
17+
This guide shows you how to build a backend for capturing, storing, and delivering survey responses using the Nitric framework. The application accepts survey submissions, generates PDF receipts, and delivers them asynchronously.
18+
19+
## API Overview
20+
21+
| **Method** | **Route** | **Description** |
22+
| ---------- | --------------- | ------------------------------------ |
23+
| `POST` | /forms/[formId] | Submit a response to a specific form |
24+
| `GET` | /receipts/[id] | Retrieve a generated receipt by ID |
25+
26+
Under the hood, the system also handles events via topics and handlers for generating PDFs and delivering them.
27+
28+
## Prerequisites
29+
30+
- [Node.js](https://nodejs.org/en/download/)
31+
- The [Nitric CLI](/get-started/installation)
32+
33+
## Project Setup
34+
35+
Create a new Nitric project using the TypeScript starter:
36+
37+
```bash
38+
nitric new surveys-backend ts-starter
39+
cd surveys-backend
40+
npm install
41+
```
42+
43+
## Add Runtime Type Safety
44+
45+
This project uses:
46+
47+
- [Zod](https://zod.dev/) to define and validate the structure of form submissions.
48+
- [pdfkit](https://pdfkit.org/) to generate pdfs from html templates.
49+
50+
### Install Zod
51+
52+
```bash
53+
npm install zod pdfkit
54+
```
55+
56+
### Define a schema for form submissions
57+
58+
Create a file at `src/forms/form/schema.ts`:
59+
60+
```ts title:src/forms/form/schema.ts
61+
import { z } from 'zod'
62+
63+
export const SubmissionSchema = z.object({
64+
name: z.string(),
65+
rating: z.number().min(1).max(5),
66+
feedback: z.string().optional(),
67+
})
68+
69+
export type Submission = z.infer<typeof SubmissionSchema>
70+
```
71+
72+
This schema ensures that any incoming submission contains a valid name, a 1–5 rating, and optional feedback.
73+
74+
## Step 1: Define Resources
75+
76+
Create and configure the cloud resources your backend will use, such as storage buckets, queues, and key-value stores.
77+
78+
```ts title:src/resources/resources.ts
79+
import { bucket, kv, topic } from '@nitric/sdk'
80+
81+
export const output = bucket('receipts').allow('read', 'write')
82+
export const submissions = kv('submissions').allow('set', 'get')
83+
export const submitted = topic('form-submitted').allow('publish')
84+
export const receipts = topic('form-submitted')
85+
```
86+
87+
## Step 2: Handle Submissions
88+
89+
Create an API route that validates and stores form submissions, then triggers further processing.
90+
91+
```ts title:src/services/forms.ts
92+
import { api } from '@nitric/sdk'
93+
import { submissions, submitted } from '../resources/resources'
94+
import { SubmissionSchema } from '../form/schema'
95+
96+
const formApi = api('forms')
97+
98+
formApi.post('/forms/:formId', async (ctx) => {
99+
const formId = ctx.req.params.formId
100+
const parsed = SubmissionSchema.safeParse(ctx.req.json())
101+
if (!parsed.success) {
102+
ctx.res.status = 400
103+
ctx.res.json({ msg: 'Invalid submission', errors: parsed.error.format() })
104+
return
105+
}
106+
const data = parsed.data
107+
const id = `${formId}-${Date.now()}`
108+
109+
await submissions.set(id, data)
110+
await submitted.publish({ id, formId })
111+
112+
ctx.res.json({ msg: 'Submission received', id })
113+
})
114+
```
115+
116+
## Step 3: Generate PDF Receipts
117+
118+
Listen for submitted events and generate a formatted PDF receipt from the stored data.
119+
120+
```ts title:src/services/pdfs.ts
121+
import { receipts, submissions, output } from '../resources/resources'
122+
import { buildReceipt } from '../form/receipt'
123+
124+
receipts.subscribe(async (ctx) => {
125+
const { id } = ctx.req.json()
126+
const submission = await submissions.get(id)
127+
128+
if (!submission) {
129+
console.error(`No submission found for ID: ${id}`)
130+
return
131+
}
132+
133+
// Build the PDF buffer from the submission data
134+
const buffer = await buildReceipt(submission)
135+
136+
// Store the PDF file in the bucket
137+
const file = output.file(`${id}.pdf`)
138+
await file.write(buffer)
139+
140+
console.log(`Receipt stored for ${id}`)
141+
})
142+
```
143+
144+
### Build the PDF Receipt
145+
146+
```ts title:src/forms/form/receipt.ts
147+
import PDFDocument from 'pdfkit'
148+
import { Submission } from './schema'
149+
150+
export const buildReceipt = async (data: Submission) => {
151+
const receipt = new PDFDocument({ bufferPages: true })
152+
153+
const doneWriting = new Promise<Buffer>((resolve) => {
154+
const buffers: Uint8Array[] = []
155+
156+
// Collect PDF output in memory as chunks
157+
receipt.on('data', buffers.push.bind(buffers))
158+
receipt.on('end', () => {
159+
const pdfData = Buffer.concat(buffers)
160+
resolve(pdfData)
161+
})
162+
163+
const { name, rating, feedback } = data
164+
165+
// Write header
166+
receipt.font('Times-Roman').fontSize(20).text('Survey - Receipt', 100, 100)
167+
168+
// Write section title
169+
receipt
170+
.font('Times-Roman')
171+
.fontSize(16)
172+
.text('Primary Applicant Details', 100, 150)
173+
174+
// Write submission details
175+
receipt
176+
.font('Times-Roman')
177+
.fontSize(12)
178+
.text(
179+
`Name: ${name}
180+
Rating: ${rating}
181+
Feedback: ${feedback || 'N/A'}`,
182+
100,
183+
175,
184+
)
185+
186+
// Finalize the PDF
187+
receipt.end()
188+
})
189+
190+
return await doneWriting
191+
}
192+
```
193+
194+
This PDF output is fairly plain, you can enhance it further using layout templates or branding.
195+
196+
## Step 4: Delivery Logic
197+
198+
Simulate or perform delivery of the receipt (e.g. via email or other downstream systems).
199+
200+
```ts title:src/services/deliver.ts
201+
import { receipts } from '../resources/resources'
202+
203+
receipts.subscribe(async (ctx) => {
204+
const { id } = ctx.req.json()
205+
206+
// Handle delivery or hook into a real email/SaaS integration
207+
console.log(`Delivering receipt for submission: ${id}`)
208+
})
209+
```
210+
211+
## Step 5: Retrieve Receipts
212+
213+
Create an endpoint that returns a download URL for the generated receipt file.
214+
215+
```ts title:src/apis/receipts.ts
216+
import { api } from '@nitric/sdk'
217+
import { output } from '../resources/resources'
218+
219+
const receiptApi = api('receipts')
220+
221+
receiptApi.get('/receipts/:id', async (ctx) => {
222+
const id = ctx.req.params.id
223+
const file = output.file(`${id}.pdf`)
224+
const url = await file.getDownloadUrl()
225+
ctx.res.body = url
226+
})
227+
```
228+
229+
## Run and Test Locally
230+
231+
```bash
232+
nitric start
233+
```
234+
235+
### Submit a Survey
236+
237+
Use the dashboard to submit your survey data -
238+
239+
```json
240+
{
241+
"name": "Jane",
242+
"rating": 5,
243+
"feedback": "Great experience!"
244+
}
245+
```
246+
247+
![Submit Form](/docs/images/guides/survey-application/submit.png)
248+
249+
You should see messages in your console for PDF generation and delivery.
250+
251+
### Get a Receipt
252+
253+
Replace `<timestamp>` with the value returned from the submission.
254+
255+
![Get receipt](/docs/images/guides/survey-application/receipt.png)
256+
257+
Use the URL in the response to retrieve your PDF:
258+
259+
![Get PDF](/docs/images/guides/survey-application/pdf.png)
260+
261+
## Summary
262+
263+
In this guide, you built a backend for handling survey submissions using Nitric and TypeScript:
264+
265+
- Created a REST API to accept form submissions
266+
- Validated user input using Zod
267+
- Stored submissions in a key-value store
268+
- Generated PDF receipts using pdfkit
269+
- Stored the receipts in cloud storage
270+
- Delivered them asynchronously via event topics
271+
- Exposed a URL endpoint to retrieve generated receipts
61.7 KB
Loading
113 KB
Loading
137 KB
Loading

0 commit comments

Comments
 (0)