AI-Assisted Full-Stack Development: From Code Generation to Architecture Design with LLMs
A practical guide to using large language models across the full stack: not just for code snippets, but for requirements clarification, technology selection, and module design. Using a Mini Program app (a waterfall wallpaper browser, user uploads, and AI image generation) as a reproducible case study, the article covers architecture-first prompting, data models and interface contracts, cursor-based pagination, secure client-side compression with direct-to-object-storage uploads, multimodal pipeline orchestration, cost controls, layered context engineering, and a verification checklist for AI-generated code. You'll also find a clear breakdown of what the model should own and where the human must make the final call.
1. Conclusion First: The Change Is in How You Think, Not How Fast You Write
After more than a year of building with LLMs, most developers say the same thing: “autocomplete got better.” But the leap in productivity comes from a change in the interaction model. The model no longer just answers narrow, local requests like “write me a carousel” and hands back a snippet you have to stitch together yourself. Instead, it takes on requirements clarification, technology selection, and module decomposition first, and only then works down into implementation.
The real difference between the two modes is local optima versus global consistency. With fragment-based generation, each piece may be reasonable on its own. Once the pieces are assembled, though, you often end up with inconsistent data models, drifting naming conventions, reinvented wheels, and error-handling strategies that don’t agree with each other. “Architecture first, implementation second” works differently. You first establish a contract that every part of the system has to follow, then make the implementation fit it.
This is best shown with a reproducible case study: a Mini Program client (a lightweight app that runs inside a host app). It has three features:
- A waterfall feed for browsing wallpapers
- User upload management
- AI image generation, covering image analysis, prompt editing, style transfer, and image generation
The tech stack is kept as generic as possible so it can be ported to any cloud environment.
2. Architecture First: Have the Model Produce a Technical Selection Brief
When you hand over requirements, don’t just say “write the code.” Ask the model to output an architecture proposal first, and only start coding after you’ve confirmed it. A useful proposal covers at least the following:
- Client-side choice: a native Mini Program framework or a cross-platform framework; page structure and navigation hierarchy (for example, three tabs at the bottom: Home, AI Studio, and Profile).
- Server-side shape: managed cloud functions, a managed database, and object storage. Together, they avoid the operational burden of running your own servers.
- Data model: collection breakdown, field types, index design, and pagination strategy.
- AI capability orchestration: which step uses a vision-language model, which uses a text model, and which uses an image generation model, plus how their inputs and outputs connect.
- Non-functional constraints: concurrency and rate limiting, timeouts and retries, cost ceilings, and content moderation.
The value of this output is that it makes implicit assumptions explicit. For example, the model may point out that uploading full-resolution images directly is both slow and expensive, so images should be compressed on the client before upload. It may also flag that AI generation takes a long time, so it must be made asynchronous and persisted with task status rather than waited on synchronously inside the request.
Tip: If your team already uses a vendor’s managed services (for example, object storage, function compute, and managed database offerings from Baidu AI Cloud), just replace the server-side row of the checklist with the corresponding product names. The overall architecture stays the same.
3. Data Model and Interface Contracts: Define the Contract Before the Code
The most common reason AI-generated code has to be reworked is that field names and structures don’t match across modules. The solution is contracts first: have the model produce type definitions and interface signatures, then review and finalize them yourself.
// Wallpaper iteminterface WallpaperItem {_id: string;title: string;coverUrl: string; // thumbnailoriginUrl: string; // original imagewidth: number;height: number;category: string;tags: string[];downloads: number;createTime: number;}// AI creation tasktype TaskStatus = 'pending' | 'analyzing' | 'ready' | 'failed';interface AITask {_id: string;openid: string;sourceUrl: string;analysis?: {subject: string[]; // main elementsstyle: string[]; // visual stylescene: string[]; // background / mood};prompt: string;resultUrl?: string;status: TaskStatus;createTime: number;}
Once the contract is finalized, have the model implement one module at a time. State clearly in the prompt: “Do not modify the type definitions. If you think a change is needed, explain why first.” This one constraint noticeably reduces cross-module rework.
4. Key Engineering Details (Where AI Often Goes Wrong)
4.1 Waterfall feed and pagination
A waterfall layout is more than two columns side by side. The hard parts are layout jitter caused by unknown image heights and cursor-based pagination. A good approach is to keep one array per column and always place each item in the currently shortest column. For pagination, use a cursor based on a timestamp or _id instead of skip, which keeps queries fast on deep pages.
// Cloud function: cursor-based pagination queryexports.getWallpapers = async (event) => {const { category, cursor, size = 20 } = event;const db = cloud.database();let q = db.collection('wallpapers').orderBy('createTime', 'desc');if (category && category !== 'all') q = q.where({ category });if (cursor) q = q.where({ createTime: _.lt(cursor) });const res = await q.limit(size + 1).get();const hasMore = res.data.length > size;const list = hasMore ? res.data.slice(0, size) : res.data;return {list,hasMore,nextCursor: hasMore ? list[list.length - 1].createTime : null,};};
// Client side: place each item in the shortest column to avoid layout jitterfunction distribute(list, columns = 2) {const buckets = Array.from({ length: columns }, () => []);const heights = new Array(columns).fill(0);list.forEach((item) => {const ratio = item.height / item.width || 1;const i = heights.indexOf(Math.min(...heights));buckets[i].push(item);heights[i] += ratio;});return buckets;}
4.2 Upload pipeline: compress → upload with temporary credentials → write metadata
Routing images through a cloud function is a classic anti-pattern: it consumes function resources and bills data transfer twice. The right approach has three steps. Compress the image on the client. Then request temporary upload credentials from the cloud function, limited by expiry time and path. Finally, upload directly to object storage; the success callback only writes metadata.
// Client-side compression: cap the longest edge and lower the qualityfunction compressImage(tempPath, maxEdge = 1600) {return new Promise((resolve, reject) => {wx.getImageInfo({src: tempPath,success: ({ width, height }) => {const scale = Math.min(1, maxEdge / Math.max(width, height));wx.compressImage({src: tempPath,quality: scale < 1 ? 70 : 85,success: (res) => resolve(res.tempFilePath),fail: reject,});},fail: reject,});});}
⚠️ Security warning: Never ship any cloud provider’s credentials to the client. Temporary credentials must be issued by the server and restricted to a directory prefix, file type, size, and expiry time. Database read and write permissions must also be checked on the server by verifying the caller’s identity. Ownership should never be decided based on parameters sent from the client.
4.3 Orchestrating a multimodal pipeline
“Upload → analyze → edit → generate” is a typical multi-model pipeline. You need to get four things right:
- Structured output. Ask the vision-language model to return a fixed set of dimensions, validate the output against a schema, and retry if parsing fails. This keeps free-form text out of the image generation model’s prompt.
Analyze the image and output JSON only, with no extra text:{"subject": string[], "style": string[], "scene": string[], "suggestedPrompt": string}Each array should have 2–5 items, and each item should be no longer than 12 characters.
- Asynchrony and a state machine. Image generation usually takes from a few seconds to tens of seconds. Submit the task and return a
taskId, then let the client poll for the result or subscribe over a long-lived connection. Persist each task’s status, and support retries and idempotency (the sameopenid+ samesourceUrl+ same prompt hits the cache and reuses the existing result). - Timeouts and graceful degradation. Give every external call its own timeout. If vision analysis fails, fall back to offering default style templates only, instead of letting the whole pipeline fail.
- Content safety. Send both input images and generated results through a moderation step. If moderation flags either one, block it and tell the user.
4.4 Cost control
AI image generation is the cost item most likely to get out of control. Set at least three limits:
- A daily quota per user
- A maximum resolution per task
- Reuse of cached results
For frequently viewed content, serve thumbnails from a CDN cache and load full-resolution images only when the user opens the preview.
5. Context Supply: The Real Driver of Output Quality
With the same model, output quality usually varies because of how you organize the context, not how you phrase the prompt. A reusable approach is to feed context in layers:
- L1 – Global constraints: an approved technology stack, directory conventions, naming conventions, and forbidden practices (for example, no hardcoded secrets, and no use of DOM or other APIs the Mini Program client doesn’t support).
- L2 – Project structure: the directory tree, a one-line description of what each directory is responsible for, and a list of existing shared modules (so the model doesn’t implement something twice).
- L3 – Contracts: type definitions, interface signatures, and error code conventions.
- L4 – Current task: the requirements for a single module, its acceptance criteria, and a short code sample that shows the expected style.
Pair these layers with three working rules:
- Work on one module at a time.
- Ask for an implementation plan before any code.
- Verify each module as soon as it’s delivered, before starting the next one.
Following these rules keeps the blast radius small when something breaks, so you never end up with “30 files failing at once.”
6. A Verification Loop: AI-Generated Code Must Pass Tools and Human Review
Treating the model as a senior colleague only works if its output goes through the normal review process. A minimal verification checklist:
| Stage | What to check | Method |
|---|---|---|
| Static quality | Type errors, unused variables, cyclomatic complexity | Type checking + linting |
| Contract consistency | Whether field names and return structures match the definitions | Type system + API tests |
| Business correctness | Pagination boundaries, empty lists, duplicate submissions | Unit tests |
| Security | Secret leakage, unauthorized read/write, injection | Manual review + secret scanning |
| Performance and cost | N+1 queries, missing indexes, uploading full-resolution images | Slow query logs + billing monitoring |
In practice, defects in AI-generated code tend to fall into four types:
- Calls to APIs or parameters that don’t exist (hallucination)
- Over-engineering, such as adding abstraction layers a single-page app doesn’t need
- Missing edge cases, such as empty arrays, very large images, or a dropped network connection
- Errors swallowed silently, such as a
catchblock that does nothing
All of these can be caught by the checks in the table before the code reaches the main branch.
7. Where the Line Between Human and Machine Falls
Positioning the model as an “architect colleague” is accurate, but the division of responsibility has to be clear:
- The model is responsible for: enumerating options, writing boilerplate code, implementing across layers, and writing documentation.
- The human is responsible for: judging whether a requirement is real, making technology trade-offs, setting the red lines for security and compliance, and signing off on the final merge.
The more you let the model generate files end to end, the more firmly you need to enforce two checkpoints: contract confirmation and step-by-step verification. These are not process overhead. They are what allows the gains from automation to actually materialize.
8. A Transferable Methodology, Summarized
- Plan before code. Ask the model to output the architecture, data model, and module list as part of the requirements. Confirm them manually before any coding starts.
- Contracts first. Treat type and interface definitions as an immutable baseline that every implementation must follow.
- Small, fast increments. Generate and verify one module at a time to keep the blast radius of failures small.
- Context engineering. Feed the model context in four layers: global constraints, project structure, contracts, and the current task.
- Engineering fundamentals still apply. Cursor-based pagination, asynchronous tasks, idempotent caching, quotas and rate limits, and never shipping credentials to the client. None of these are waived just because AI is involved.
- A verification loop is non-negotiable. Type checking, tests, and security scanning are the minimum bar for AI-generated code to enter the main branch.
Tooling will keep changing quickly, but the core workflow stays the same: clarify requirements → define contracts → implement step by step → verify automatically. Build it into your team’s process, and the gains from AI-assisted development stay reproducible no matter which underlying model you switch to.
