How a real booking website — landing page, database, live payments, deployed — gets built with Claude Code in a single working session. Written from one that happened.
เว็บจองที่นั่งจริง ๆ หน้าเว็บ ฐานข้อมูล ระบบรับเงิน ขึ้นออนไลน์ครบ สร้างด้วย Claude Code จบในการทำงานรอบเดียว เขียนจากงานที่เกิดขึ้นจริง
This is a method, not a tutorial. Tutorials go stale the week the API changes. A method survives, because it is about how you spend your attention.
นี่คือวิธีการ ไม่ใช่บทเรียนสอนทำตามทีละขั้น บทเรียนหมดอายุตั้งแต่ API เปลี่ยนสัปดาห์ถัดมา แต่วิธีการอยู่ได้ เพราะมันว่าด้วยการใช้สมาธิของคุณ
Everything here comes from building one specific thing: a seven-day bootcamp website with a booking system that takes real money. Claude Code did the building, Supabase held the data, Stripe took the payments, Vercel served it. Nothing about that combination is precious. Swap any piece and the method holds.
ทุกอย่างในนี้มาจากการสร้างของจริงชิ้นเดียว เว็บค่าย 7 วัน พร้อมระบบจองที่รับเงินได้จริง Claude Code เป็นคนสร้าง Supabase เก็บข้อมูล Stripe รับเงิน Vercel เอาขึ้นเว็บ ชุดเครื่องมือนี้ไม่ได้ศักดิ์สิทธิ์ เปลี่ยนตัวไหนก็ได้ วิธีการยังใช้ได้เหมือนเดิม
What is precious is the sequence, and the discipline of checking your work inside the same breath as doing it. That is the whole lesson. The rest is detail.
ที่สำคัญคือลำดับ กับวินัยในการตรวจงานตัวเองในลมหายใจเดียวกับตอนที่ทำ นั่นแหละคือบทเรียนทั้งหมด ที่เหลือเป็นรายละเอียด
| TRACK A | Beginner. You have an idea and little or no code. These sections cover talking to the model well, knowing when to trust an answer, and building the habit of verifying. You can ship a real product without writing a line yourself. | สายเริ่มต้น มีไอเดีย แต่เขียนโค้ดไม่เป็นหรือเป็นนิดหน่อย ส่วนนี้ว่าด้วยการคุยกับโมเดลให้เป็น รู้ว่าเมื่อไหร่ควรเชื่อคำตอบ และสร้างนิสัยตรวจงาน คุณปล่อยของจริงได้โดยไม่ต้องพิมพ์โค้ดเองสักบรรทัด |
| TRACK B | Builder. You already ship software. These are the engineering sections: concurrency under load, security posture, idempotent payment handling, and what is worth testing when a model wrote the code. | สายบิลด์เดอร์ ปล่อยซอฟต์แวร์เป็นอยู่แล้ว ส่วนนี้คือเรื่องวิศวกรรม การชนกันของคำสั่งพร้อมกัน ท่าตั้งรับด้านความปลอดภัย การรับ webhook แบบซ้ำแล้วไม่พัง และอะไรที่ควรเทสต์เมื่อโมเดลเป็นคนเขียนโค้ด |
| BOTH | Everyone. The method chapters and the economics at the end. | ทุกคน บทว่าด้วยวิธีการ กับบทต้นทุนตอนท้าย |
The model is fast at producing and slow at knowing. Your job is not to type less — it is to close the gap between what was produced and what is true, as early and as cheaply as possible.
โมเดลผลิตเร็ว แต่รู้ช้า หน้าที่คุณไม่ใช่พิมพ์ให้น้อยลง แต่คือปิดช่องว่างระหว่างสิ่งที่มันผลิตออกมา กับสิ่งที่จริง ให้เร็วที่สุดและถูกที่สุด
The sun lands in front of the house. Everyone stops. Nobody planned that part.
ดวงอาทิตย์ตกตรงหน้าบ้านพอดี ทุกคนหยุดมือ ไม่มีใครวางแผนตรงนี้ไว้
Beginners assume the bottleneck is typing speed. It never is. The bottleneck is rework — building the wrong thing, then building it again.
มือใหม่มักคิดว่าคอขวดคือความเร็วในการพิมพ์ ไม่เคยใช่เลย คอขวดคือการทำซ้ำ สร้างผิด แล้วสร้างใหม่
Watch anyone build with an AI assistant for an hour and the shape is always the same. A burst of production. A pause. Then a correction that throws away most of the burst. The generated code was never the expensive part. The expensive part was that nobody established what "right" meant before the generating started.
ลองนั่งดูใครสักคนใช้ AI ทำงานสักชั่วโมง รูปแบบจะเหมือนกันทุกครั้ง พุ่งออกมาชุดใหญ่ หยุด แล้วแก้ทีเดียวทิ้งของที่พุ่งออกมาเกือบหมด โค้ดที่โมเดลเขียนไม่เคยเป็นส่วนที่แพง ส่วนที่แพงคือไม่มีใครตกลงกันก่อนว่า "ถูก" แปลว่าอะไร
Three things cause almost all rework:
สามอย่างนี้คือต้นเหตุของการทำซ้ำเกือบทั้งหมด
Partway through, a deployment failed with an error about function runtimes. The instinct is to guess — try a different value, redeploy, wait, guess again. Each cycle costs minutes and a deploy.
ระหว่างทาง การดีพลอยพังพร้อมข้อความว่าด้วย function runtime สัญชาตญาณคือเดา ลองค่าอื่น ดีพลอยใหม่ รอ แล้วเดาอีก แต่ละรอบกินเวลาหลายนาทีบวกอีกหนึ่งดีพลอย
What actually happened: the platform's own configuration schema was fetched and read. It defined the field as "the npm package name of a Runtime, including its version." The value in the file wasn't an npm package name. One read, one fix — and the fix came with a reason that made it obvious why guessing would never have worked.
สิ่งที่เกิดขึ้นจริงคือ ไปดึง schema ของแพลตฟอร์มมาอ่าน มันนิยามฟิลด์นั้นว่า "ชื่อ npm package ของ Runtime พร้อมเวอร์ชัน" ค่าที่อยู่ในไฟล์ไม่ใช่ชื่อ npm package อ่านครั้งเดียว แก้ครั้งเดียว แล้วยังได้เหตุผลที่ทำให้เห็นชัดว่าเดายังไงก็ไม่มีทางถูก
Reading the spec took less time than one wrong guess. That is the discipline in miniature.
อ่านสเปกใช้เวลาน้อยกว่าเดาผิดหนึ่งครั้ง นี่คือวินัยทั้งหมดในรูปย่อ
Take your last AI-assisted project. Count the times you said "no, actually…" and had output thrown away. For each one, write the single sentence that, said earlier, would have prevented it. That list is your personal brief template.
หยิบโปรเจกต์ล่าสุดที่ใช้ AI ช่วยขึ้นมา นับดูว่าพูดคำว่า "ไม่ใช่ เอาจริง ๆ คือ…" กี่ครั้ง แล้วต้องทิ้งของที่ทำไปแล้วกี่ครั้ง แต่ละครั้งเขียนประโยคเดียวที่ถ้าพูดตั้งแต่แรกจะไม่เกิดเรื่องนี้ รายการนั้นแหละคือเทมเพลตบรีฟส่วนตัวของคุณ
Nothing is being built in this photograph. That is rather the point.
ในรูปนี้ไม่มีใครสร้างอะไรเลย และนั่นแหละคือประเด็น
One cycle, repeated at every scale — from a whole feature down to a single function. Skipping any step is what costs you the day.
วงจรเดียว ใช้ซ้ำได้ทุกขนาด ตั้งแต่ทั้งฟีเจอร์ลงมาถึงฟังก์ชันเดียว ข้ามขั้นไหนก็ตาม นั่นแหละที่กินเวลาทั้งวันของคุณ
Frame before Load. If you don't know what you're building, no amount of style guidance helps. Framing is one minute and it is the highest-leverage minute of the session.
กรอบมาก่อนมาตรฐาน ถ้ายังไม่รู้ว่ากำลังสร้างอะไร ให้แนวทางสไตล์ไปเท่าไหร่ก็ไม่ช่วย การตั้งกรอบใช้เวลานาทีเดียว และเป็นนาทีที่คุ้มที่สุดของทั้งรอบ
Load before Build. Standards given after the fact mean a rewrite. Standards given before cost nothing extra — they simply shape the first draft.
มาตรฐานมาก่อนลงมือ ให้มาตรฐานทีหลังเท่ากับเขียนใหม่ ให้ก่อนไม่มีค่าใช้จ่ายเพิ่ม มันแค่ไปกำหนดร่างแรกเท่านั้น
Verify before you move on. This is the step people skip and the one that pays. A wrong answer caught in the same turn costs one correction. Caught three features later, it costs four.
ตรวจก่อนไปต่อ ขั้นนี้คนข้ามบ่อยที่สุด และเป็นขั้นที่คุ้มที่สุด จับคำตอบผิดได้ในรอบเดียวกัน เสียแค่การแก้หนึ่งครั้ง ไปจับได้ตอนทำฟีเจอร์ที่สามแล้ว เสียสี่ครั้ง
Name the gaps out loud. Anything invented, stubbed or untested gets written down the moment it's created. In the build this manual describes, the page contained several plausible-sounding facts — internet speeds, distances, a cohort count — that were pure invention. They were listed in the README on the day they were written, so they could not quietly become true by repetition.
พูดออกมาเลยว่าอะไรยังโหว่ อะไรที่แต่งขึ้น ยังเป็นของหลอก หรือยังไม่ได้เทสต์ จดไว้ทันทีที่สร้างมัน ในงานที่คู่มือนี้เล่าถึง หน้าเว็บมีข้อเท็จจริงที่ฟังดูน่าเชื่ออยู่หลายจุด ความเร็วเน็ต ระยะทาง จำนวนรุ่นที่จัดไปแล้ว ทั้งหมดแต่งขึ้นล้วน ๆ ของพวกนี้จดลง README ตั้งแต่วันเดียวกับที่เขียน มันจะได้ไม่ค่อย ๆ กลายเป็นเรื่องจริงเพราะพูดซ้ำไปเรื่อย ๆ
A fabricated fact that nobody wrote down is indistinguishable from a real one within an hour. Write it down when you make it.
ข้อมูลที่แต่งขึ้นแล้วไม่มีใครจด ภายในชั่วโมงเดียวจะแยกไม่ออกจากของจริง แต่งเมื่อไหร่ จดเมื่อนั้น
| You builtสิ่งที่สร้าง | Verify byตรวจด้วย | Costใช้เวลา | |||
|---|---|---|---|---|---|
| A page | หน้าเว็บ | Count opening and closing tags; check every link resolves | นับแท็กเปิดกับแท็กปิด เช็กว่าทุกลิงก์ไปถึงไฟล์จริง | Seconds | ไม่กี่วินาที |
| A database schema | โครงฐานข้อมูล | Run the migrations against a real database | รัน migration กับฐานข้อมูลจริง | A minute | ราวหนึ่งนาที |
| A logic rule | กฎทางตรรกะ | Write the test that tries to break it | เขียนเทสต์ที่พยายามทำให้มันพัง | Minutes | ไม่กี่นาที |
| A config file | ไฟล์คอนฟิก | Fetch the platform's schema and check each field | ดึง schema ของแพลตฟอร์มมาเทียบทีละฟิลด์ | A minute | ราวหนึ่งนาที |
| A factual claim | ข้อมูลที่อ้าง | Find the source, or mark it unverified | หาแหล่งอ้างอิง ถ้าไม่มีก็ระบุว่ายังไม่ได้ตรวจ | Seconds | ไม่กี่วินาที |
Every row costs less than the rework it prevents. That asymmetry is the whole argument.
ทุกแถวราคาถูกกว่างานที่ต้องทำซ้ำถ้าไม่ตรวจ ความไม่สมมาตรตรงนี้คือเหตุผลทั้งหมด
The boat comes twice a day. Everything else you decide yourself.
เรือเข้าวันละสองรอบ นอกนั้นคุณตัดสินใจเองทั้งหมด
Sixty seconds of setup that removes most of the rework from the rest of the day.
ตั้งต้นหกสิบวินาที ตัดงานทำซ้ำออกไปเกือบหมดตลอดทั้งวันที่เหลือ
Write these four lines before your first prompt. Not in your head — written down.
เขียนสี่บรรทัดนี้ก่อนพิมพ์พร้อมต์แรก อย่าคิดในหัว เขียนลงจริง ๆ
| WHO | One reader. A person, not a segment. "A developer with a half-finished side project" beats "our target audience." | คนอ่านคนเดียว คนจริง ๆ ไม่ใช่กลุ่มเป้าหมาย "นักพัฒนาที่มีโปรเจกต์ข้างค้างอยู่ครึ่งทาง" ดีกว่า "กลุ่มลูกค้าของเรา" |
| WHAT | What is different after they use this. A belief changed, an action taken, a thing bought. | อะไรเปลี่ยนไปหลังเขาใช้ ความเชื่อเปลี่ยน ลงมือทำอะไรสักอย่าง หรือซื้อของ |
| SHAPE | The deliverable, concretely. One HTML file. A Postgres schema. A deck of twelve slides. | ของที่ส่งมอบ ให้ชัดเป็นรูปธรรม ไฟล์ HTML หนึ่งไฟล์ โครง Postgres สไลด์สิบสองหน้า |
| DONE | The test you'd run to prove it works. If you can't name one, you don't know what you're building. | เทสต์ที่จะใช้พิสูจน์ว่ามันทำงาน ถ้าบอกไม่ได้สักอัน แปลว่ายังไม่รู้ว่ากำลังสร้างอะไร |
WHO — an indie developer with a project they keep not finishing, browsing on a phone at night.
WHAT — they book a seat, or they message us. Nothing else counts.
SHAPE — one landing page, one booking form, real payments. Static files, no framework.
DONE — a stranger can pick a week, pay with a real card, and the seat count drops by one.
WHO — นักพัฒนาอิสระที่มีโปรเจกต์ทำไม่จบสักที นั่งเลื่อนดูในมือถือตอนกลางคืน
WHAT — เขาจองที่นั่ง หรือทักมาหาเรา นอกนั้นไม่นับ
SHAPE — หน้าแลนดิ้งหนึ่งหน้า ฟอร์มจองหนึ่งอัน รับเงินได้จริง ไฟล์นิ่ง ไม่ใช้เฟรมเวิร์ก
DONE — คนแปลกหน้าเลือกสัปดาห์ได้ จ่ายด้วยบัตรจริงได้ แล้วจำนวนที่นั่งลดลงหนึ่ง
That last line drove every technical decision that followed.
บรรทัดสุดท้ายนั่นแหละที่กำหนดการตัดสินใจทางเทคนิคทุกอย่างที่ตามมา
If you're not sure what to specify, hand the problem back:
ถ้าไม่แน่ใจว่าต้องระบุอะไรบ้าง โยนโจทย์กลับไป
Before you build anything, ask me the three or four questions whose answers would most change what you build. Wait for my answers.
ก่อนจะเริ่มสร้างอะไร ถามฉันมาสามสี่ข้อ เอาเฉพาะข้อที่ถ้าได้คำตอบแล้วจะเปลี่ยนสิ่งที่คุณสร้างมากที่สุด แล้วรอคำตอบก่อน อย่าเพิ่งลงมือ
You'll get better questions than you would have thought to answer, and you'll get them before anything is produced. The questions are often the most useful output of the session.
คุณจะได้คำถามที่ดีกว่าที่ตัวเองนึกออก และได้มาก่อนที่จะมีอะไรออกมาสักชิ้น หลายครั้งคำถามพวกนี้คือผลลัพธ์ที่มีประโยชน์ที่สุดของทั้งรอบ
Anything you'd say twice belongs in a reusable instruction set. Design rules, writing voice, testing conventions, security posture. Load them at the start and every later output inherits them.
อะไรที่ต้องพูดสองรอบ ควรไปอยู่ในชุดคำสั่งที่ใช้ซ้ำได้ กฎดีไซน์ น้ำเสียงการเขียน ธรรมเนียมการเทสต์ ท่าตั้งรับด้านความปลอดภัย ป้อนตอนเริ่ม แล้วผลลัพธ์ทุกชิ้นหลังจากนั้นรับไปเอง
In this build, three sets were loaded before the first line of HTML: a design system with concrete values, a writing standard for the copy, and an animation reference for the motion. The result is that "make it look more premium" was never said, because premium was the starting definition rather than a later correction.
ในงานนี้ ป้อนไปสามชุดก่อนเขียน HTML บรรทัดแรก ระบบดีไซน์ที่มีค่าตัวเลขชัด มาตรฐานการเขียนสำหรับคอปปี้ และตัวอ้างอิงเรื่องแอนิเมชัน ผลคือไม่เคยต้องพูดคำว่า "ทำให้ดูพรีเมียมกว่านี้" เลย เพราะพรีเมียมเป็นนิยามตั้งต้น ไม่ใช่คำสั่งแก้ทีหลัง
Standards stated after the output are a rewrite. Standards stated before are free.
บอกมาตรฐานหลังได้ของ เท่ากับเขียนใหม่ บอกก่อน ไม่เสียอะไรเลย
Describing a reference in words is lossy and expensive. Handing over the actual thing is cheap and exact. A screenshot carries the palette, type scale, density and spacing in one shot. A pasted error carries the exact failure. A link to real documentation ends an argument about what a field means.
อธิบายตัวอย่างอ้างอิงด้วยคำพูด ข้อมูลหายและเปลืองด้วย ส่งของจริงไปเลยถูกกว่าและตรงกว่า ภาพหน้าจอหนึ่งภาพพาชุดสี ขนาดตัวอักษร ความแน่น และระยะห่างไปพร้อมกันหมด ข้อความ error ที่วางไปพาความพังที่แท้จริงไป ลิงก์เอกสารจริงจบการเถียงว่าฟิลด์นั้นแปลว่าอะไร
| Instead ofแทนที่จะบอกว่า | Do thisให้ทำแบบนี้ | ||
|---|---|---|---|
| "Make it look like a premium retreat site" | "ทำให้เหมือนเว็บรีทรีตหรู ๆ" | Paste a screenshot of one | วางภาพหน้าจอของเว็บนั้นไปเลย |
| "The build is failing somehow" | "มัน build ไม่ผ่าน ไม่รู้ทำไม" | Paste the exact error text | วางข้อความ error ตัวเต็มไป |
| "Use the standard config for this" | "ใช้คอนฟิกมาตรฐานของอันนี้" | Link the platform's own schema | ส่งลิงก์ schema ของแพลตฟอร์มไป |
| "Match our brand voice" | "ให้เข้ากับน้ำเสียงแบรนด์เรา" | Paste three paragraphs you've written | วางงานเขียนของคุณเองไปสามย่อหน้า |
Four lines, written by hand, before a single prompt is typed.
สี่บรรทัด เขียนด้วยมือ ก่อนพิมพ์พร้อมต์แรกสักตัว
A landing page that doesn't look generated, built as a single file, verified before anyone looks at it.
หน้าแลนดิ้งที่ไม่ดูเหมือนของที่ AI ปั๊มออกมา ทำเป็นไฟล์เดียว ตรวจให้เรียบร้อยก่อนใครจะเห็น
The site in this manual is plain HTML with the CSS and JavaScript inside it. No build step, no bundler, no dependencies for the pages themselves. This is not nostalgia. It is a deliberate reduction of the surface where things go wrong.
เว็บในคู่มือนี้คือ HTML ล้วน ๆ มี CSS กับ JavaScript อยู่ในไฟล์เดียวกัน ไม่มีขั้นตอน build ไม่มี bundler ตัวหน้าเว็บเองไม่มี dependency นี่ไม่ใช่การโหยหาของเก่า แต่เป็นการจงใจลดพื้นที่ที่อะไร ๆ จะพังได้
| Choiceตัวเลือก | What it removesตัดอะไรออกไป | ||
|---|---|---|---|
| No build step | ไม่มีขั้นตอน build | Build failures. There is nothing to build. | build พัง เพราะไม่มีอะไรให้ build |
| No framework | ไม่ใช้เฟรมเวิร์ก | Version conflicts, breaking upgrades, framework-shaped bugs | เวอร์ชันชนกัน อัปเกรดแล้วพัง บั๊กที่มาจากตัวเฟรมเวิร์กเอง |
| Inline CSS and JS | CSS กับ JS อยู่ในไฟล์ | Broken relative paths, missing assets, load-order bugs | พาธเพี้ยน ไฟล์หาย ลำดับการโหลดผิด |
Generated design has a signature: pure white background, one particular blue, a single sans-serif everywhere, uniform rounded corners, everything centred. Avoiding it is a small number of concrete decisions.
ดีไซน์ที่ AI ปั๊มออกมามีลายเซ็นของมัน พื้นขาวสนิท สีน้ำเงินเฉดหนึ่งที่เห็นบ่อย ฟอนต์ sans-serif ตัวเดียวทั้งหน้า มุมโค้งเท่ากันหมด แล้วจัดกึ่งกลางทุกอย่าง เลี่ยงได้ด้วยการตัดสินใจไม่กี่ข้อ
The failure mode of generated copy is not bad grammar. It is the absence of anything specific enough to be wrong.
จุดพังของคอปปี้ที่ AI เขียน ไม่ใช่ไวยากรณ์ผิด แต่คือไม่มีอะไรเฉพาะเจาะจงพอที่จะผิดได้เลยสักอย่าง
Be specific or cut it. Every abstract noun should become something you could photograph or count. "Fast loading" becomes "it loads in 200ms; it used to hang for three seconds." If a sentence would survive being pasted into a different company's website, it is saying nothing.
เจาะจง หรือตัดทิ้ง คำนามนามธรรมทุกคำควรกลายเป็นของที่ถ่ายรูปได้หรือนับได้ "โหลดเร็ว" กลายเป็น "โหลดใน 200ms เมื่อก่อนค้างสามวินาที" ถ้าประโยคไหนย้ายไปวางในเว็บของบริษัทอื่นแล้วยังใช้ได้ แปลว่าประโยคนั้นไม่ได้พูดอะไรเลย
Break the rhythm. Uniform sentence length is the deepest tell, felt before it can be named. Follow a long sentence with a short one. Read it aloud — one speed throughout reads as machine-made.
อย่าให้จังหวะเท่ากันหมด ประโยคยาวเท่ากันทุกประโยคคือสัญญาณที่ลึกที่สุด คนรู้สึกได้ก่อนที่จะบอกถูกว่ามันคืออะไร ประโยคยาวแล้วต่อด้วยประโยคสั้น ลองอ่านออกเสียง ถ้าความเร็วเท่ากันตลอด แปลว่าอ่านแล้วเหมือนเครื่องเขียน
# 1. do the tags balance? grep -o '<div' index.html | wc -l grep -o '</div>' index.html | wc -l # 2. does every internal link point at a file that exists? grep -o 'href="/[^"]*"' index.html | sort -u # 3. does every image have alt text? grep -c '<img' index.html
# 1. แท็กเปิดกับแท็กปิดเท่ากันไหม grep -o '<div' index.html | wc -l grep -o '</div>' index.html | wc -l # 2. ลิงก์ภายในทุกอันชี้ไปที่ไฟล์ที่มีอยู่จริงไหม grep -o 'href="/[^"]*"' index.html | sort -u # 3. รูปทุกรูปมี alt text ไหม grep -c '<img' index.html
In this build the three checks ran after each rewrite. The link check caught six dead placeholder links that would otherwise have shipped.
ในงานนี้ สามข้อนี้รันทุกครั้งหลังเขียนใหม่ ข้อเช็กลิงก์จับลิงก์หลอกที่ตายแล้วได้หกอัน ไม่งั้นก็หลุดขึ้นเว็บไปหมดแล้ว
Ask for the check in the same message as the work. "Build the page, then confirm the tags balance and every link resolves." One turn, not two.
สั่งตรวจไปในข้อความเดียวกับสั่งงาน "ทำหน้านี้ แล้วยืนยันว่าแท็กครบและทุกลิงก์ไปถึงจริง" หนึ่งรอบ ไม่ใช่สองรอบ
Ten beds. The rule that must never break.
เตียงสิบเตียง กฎที่ห้ามพังเด็ดขาด
Where the real thinking happens. Get this wrong and no amount of application code saves you.
ตรงนี้คือจุดที่ต้องคิดจริง ๆ ถ้าพลาดตรงนี้ เขียนโค้ดฝั่งแอปเท่าไหร่ก็ช่วยไม่ได้
The product had ten beds per cohort. That is not a display detail — it is a rule the system must never violate, no matter how many people click at once.
สินค้านี้มีเตียงสิบเตียงต่อหนึ่งรุ่น นั่นไม่ใช่แค่ตัวเลขที่เอาไว้โชว์ แต่เป็นกฎที่ระบบห้ามละเมิด ไม่ว่าจะมีคนกดพร้อมกันกี่คนก็ตาม
Ask what must never be true. Then make the database itself refuse it. Application logic is a request; a constraint is a guarantee.
ถามตัวเองว่าอะไรที่ห้ามเป็นจริงเด็ดขาด แล้วให้ฐานข้อมูลเป็นคนปฏิเสธเอง ตรรกะฝั่งแอปคือการขอ แต่ constraint คือการรับประกัน
constraint inventory_no_oversell check (beds_taken <= beds_total)
That one line means overselling is physically impossible. Not unlikely — impossible. You could delete the entire application layer, connect directly with a database client, and still fail to oversell a cohort.
บรรทัดเดียวนั้นทำให้การขายเกินเป็นไปไม่ได้ในทางกายภาพ ไม่ใช่แค่โอกาสน้อย แต่เป็นไปไม่ได้ ต่อให้ลบโค้ดฝั่งแอปทิ้งทั้งชั้น แล้วต่อเข้าฐานข้อมูลตรง ๆ ก็ยังขายเกินไม่ได้อยู่ดี
Seat inventory is the classic race. Two people click "book" on the last bed at the same instant. The naive approach reads the count, decides, then writes — and there is a gap between the read and the write where both requests see one bed free.
จำนวนที่นั่งคือเคสคลาสสิกของการชนกัน สองคนกด "จอง" เตียงสุดท้ายพร้อมกันเป๊ะ วิธีที่คิดง่าย ๆ คืออ่านจำนวน ตัดสินใจ แล้วเขียนกลับ แต่ระหว่างอ่านกับเขียนมีช่องว่างอยู่ ตรงช่องนั้นทั้งสองคำขอเห็นเตียงว่างเหมือนกัน
update cohort_inventory set beds_taken = beds_taken + 1 where cohort_id = $1 and track = $2 and beds_taken < beds_total; -- zero rows updated means sold out
update cohort_inventory set beds_taken = beds_taken + 1 where cohort_id = $1 and track = $2 and beds_taken < beds_total; -- ไม่มีแถวไหนถูกอัปเดต แปลว่าเต็มแล้ว
Under Postgres's default isolation level, when two transactions update the same row the second
blocks until the first commits, then re-evaluates its WHERE clause against the row
as it now stands. If that pushed it to capacity, the condition no longer matches, zero rows
update, and you return "sold out." No advisory locks. No retry loop. One statement, correct
under any amount of concurrency.
ที่ระดับ isolation ค่าเริ่มต้นของ Postgres เมื่อสอง transaction อัปเดตแถวเดียวกัน
ตัวที่สองจะถูกบล็อกไว้จนตัวแรก commit เสร็จ แล้วค่อยประเมินเงื่อนไข WHERE
กับแถวในสภาพล่าสุดอีกที ถ้าตอนนั้นเต็มพอดี เงื่อนไขก็ไม่ตรงแล้ว
ไม่มีแถวไหนถูกอัปเดต แล้วคุณก็ตอบกลับไปว่า "เต็มแล้ว"
ไม่ต้องใช้ advisory lock ไม่ต้องวนลูป retry คำสั่งเดียว ถูกต้องไม่ว่าจะมีคนพร้อมกันเท่าไหร่
When a model hands you a correct-looking solution to a concurrency problem, that is exactly when to slow down. Ask it to explain why it is correct at the isolation level you are actually running. If the explanation is vague, the code is probably wrong.
เวลาโมเดลยื่นทางแก้ปัญหาการชนกันที่ดูถูกต้องมาให้ นั่นแหละคือจังหวะที่ต้องช้าลง ให้มันอธิบายว่าทำไมถึงถูกต้อง ที่ระดับ isolation ที่คุณใช้อยู่จริง ถ้าคำอธิบายคลุมเครือ โค้ดนั้นน่าจะผิด
Managed Postgres services hand your browser a public key. That key ships in your JavaScript; anyone can read it out of developer tools. This is by design. What stops a stranger downloading your entire bookings table with it is row-level security, and nothing else.
บริการ Postgres แบบ managed จะยื่น public key ให้เบราว์เซอร์ของคุณ คีย์นั้นติดไปกับ JavaScript ใครก็เปิด developer tools อ่านได้ และตั้งใจให้เป็นแบบนั้นตั้งแต่แรก สิ่งเดียวที่กันไม่ให้คนแปลกหน้าโหลดตาราง bookings ทั้งตารางไปด้วยคีย์นั้น คือ row-level security ไม่มีอย่างอื่น
| Tableตาราง | Public role canสิทธิ์ของ public | |
|---|---|---|
| cohorts | read open ones | อ่านรุ่นที่ยังเปิดอยู่ |
| availability | read remaining seat counts | อ่านจำนวนที่นั่งที่เหลือ |
| prices | read active prices | อ่านราคาที่ใช้งานอยู่ |
| bookings | nothing at all | ไม่ได้เลยสักอย่าง |
| payment events | nothing at all | ไม่ได้เลยสักอย่าง |
Connect with the public key and try to read your bookings table. If anything comes back, you have a data breach waiting for its first customer. Make this a step in your launch checklist, not a thing you assume.
ลองต่อด้วย public key แล้วอ่านตาราง bookings ดู ถ้ามีอะไรกลับมา แปลว่าคุณมีข้อมูลรั่วรออยู่แล้ว รอแค่ลูกค้าคนแรก ใส่ข้อนี้ลงเช็กลิสต์ก่อนเปิดจริง อย่าเดาเอาเอง
Single-process Postgres cannot demonstrate true parallel connections. The blocking behaviour above is documented semantics, not something the tests observed. The constraint was tested; the isolation behaviour was reasoned about. Those are different kinds of confidence and students should learn to tell them apart.
Postgres ที่รันโปรเซสเดียวสาธิตการเชื่อมต่อพร้อมกันจริง ๆ ไม่ได้ พฤติกรรมการบล็อกข้างบนเป็นสิ่งที่เอกสารระบุไว้ ไม่ใช่สิ่งที่เทสต์เห็นกับตา ตัว constraint ถูกเทสต์จริง ส่วนพฤติกรรม isolation เป็นการให้เหตุผล ความมั่นใจสองแบบนี้ไม่เหมือนกัน และผู้เรียนควรแยกให้ออก
You can see straight to the bottom here. Your payment code should read the same way.
น้ำตรงนี้ใสจนมองเห็นพื้น โค้ดรับเงินของคุณควรอ่านแล้วรู้สึกแบบเดียวกัน
Payments are where sloppiness stops being embarrassing and starts being expensive. Four rules cover almost everything.
ระบบเงินคือจุดที่ความมักง่ายเลิกเป็นแค่เรื่องน่าอาย แล้วกลายเป็นเรื่องแพง สี่กฎนี้ครอบคลุมเกือบทั้งหมด
1. validate the request 2. reserve the seat in your database ← before the payment provider 3. create the checkout session 4. record the session id on the booking
1. ตรวจความถูกต้องของคำขอ 2. กันที่นั่งไว้ในฐานข้อมูล ← ก่อนติดต่อผู้ให้บริการรับเงิน 3. สร้าง checkout session 4. บันทึก session id ลงในรายการจอง
Reserving before you contact the payment provider means two people cannot both reach checkout for the last seat. If step three fails, the reservation expires by itself after thirty minutes and the seat returns. No compensating transaction, no cleanup job.
กันที่นั่งไว้ก่อนติดต่อผู้ให้บริการรับเงิน แปลว่าสองคนจะไปถึงหน้าจ่ายเงินของที่นั่งสุดท้ายพร้อมกันไม่ได้ ถ้าขั้นที่สามพัง การกันที่จะหมดอายุเองใน 30 นาที แล้วที่นั่งก็กลับคืนมา ไม่ต้องมี transaction ชดเชย ไม่ต้องมีงานตามเก็บกวาด
The client sends a cohort, a track and a tier. It never sends an amount. The server looks the price up in its own table and charges that. This is the difference between a payment system and a donation box, and it is the most common serious flaw in tutorial payment code.
ฝั่งไคลเอนต์ส่งมาแค่รุ่น สาย และระดับแพ็กเกจ ไม่เคยส่งจำนวนเงินมา เซิร์ฟเวอร์ไปเปิดราคาจากตารางของตัวเองแล้วเรียกเก็บตามนั้น นี่คือเส้นแบ่งระหว่างระบบรับเงินกับกล่องรับบริจาค และเป็นช่องโหว่ร้ายแรงที่พบบ่อยที่สุดในโค้ดสอนทำระบบจ่ายเงิน
The success page is a redirect the customer's browser performs. Browsers get closed, tabs crash, phones die on hotel wifi, and anyone can type your success URL directly into the address bar. None of that is a payment.
หน้า success คือการ redirect ที่เบราว์เซอร์ลูกค้าเป็นคนทำ เบราว์เซอร์ปิดเมื่อไหร่ก็ได้ แท็บแครชได้ มือถือดับคาไวไฟโรงแรมได้ และใครก็พิมพ์ URL หน้า success ลงช่องที่อยู่เองได้ ไม่มีอันไหนคือการจ่ายเงิน
Verify the signature against the raw request body. Parse the JSON first and the signature will never match, because verification runs over the exact bytes that were sent. This detail costs more lost hours than any other part of payment integration.
ตรวจลายเซ็นกับ เนื้อคำขอดิบ ถ้าแปลง JSON ก่อน ลายเซ็นจะไม่มีวันตรง เพราะการตรวจทำบนไบต์ชุดเดิมเป๊ะ ๆ ที่ส่งมา รายละเอียดข้อนี้กินเวลาคนไปมากกว่าส่วนอื่นของงานเชื่อมระบบจ่ายเงินทั้งหมด
Payment providers guarantee at-least-once delivery. Receiving the same event twice is ordinary operation, not an error. Design for it in two layers.
ผู้ให้บริการรับเงินรับประกันการส่งแบบอย่างน้อยหนึ่งครั้ง การได้รับ event เดิมสองรอบคือการทำงานปกติ ไม่ใช่ข้อผิดพลาด ออกแบบรับไว้สองชั้น
-- layer one: a ledger with a primary key on the provider's event id insert into payment_events (id, type) values ($1, $2); -- a duplicate raises a unique violation; return 200 and stop -- layer two: make the update itself idempotent update bookings set status = 'paid' where session_id = $1 and status = 'held'; ← re-running matches zero rows
-- ชั้นที่หนึ่ง สมุดบัญชีที่ใช้ event id ของผู้ให้บริการเป็น primary key insert into payment_events (id, type) values ($1, $2); -- ถ้าซ้ำจะเกิด unique violation ให้ตอบ 200 แล้วจบ -- ชั้นที่สอง ทำให้ตัวอัปเดตเองรันซ้ำได้ update bookings set status = 'paid' where session_id = $1 and status = 'held'; ← รันซ้ำจะไม่ตรงกับแถวไหนเลย
Then test it: fire the identical event twice and assert the paid timestamp did not move.
แล้วเทสต์จริง ยิง event เดิมสองรอบ แล้วยืนยันว่าเวลาที่บันทึกว่าจ่ายแล้วไม่ขยับ
Build the entire thing on test keys. Switch to live only after a complete run-through, and expect to update the webhook signing secret when you do — test and live have different secrets, and the resulting signature failures look exactly like an attack.
สร้างทั้งหมดบนคีย์ทดสอบ เปลี่ยนไปคีย์จริงหลังซ้อมครบวงจรแล้วเท่านั้น และเตรียมใจว่าต้องเปลี่ยน secret ของ webhook ด้วย เพราะโหมดทดสอบกับโหมดจริงใช้ secret คนละตัว แล้วอาการลายเซ็นไม่ตรงที่ตามมา หน้าตาเหมือนถูกโจมตีเป๊ะ
The part nobody photographs: it works, and then you leave it running.
ตอนที่ไม่มีใครถ่ายรูปเก็บไว้ มันใช้งานได้แล้ว แล้วคุณก็ปล่อยให้มันวิ่งต่อไป
Deployment should be boring. If it is exciting, something is configured wrong.
การดีพลอยควรน่าเบื่อ ถ้ามันตื่นเต้น แปลว่ามีอะไรตั้งค่าผิด
Deployment configuration is where guessing is most tempting and least effective. The field names look obvious and frequently are not. In this build a config field was set to a value that looked entirely reasonable and was rejected. Fetching the platform's published JSON schema settled it in one read: the field expected an npm package identifier, not a version label. The correct answer was to omit the field entirely.
คอนฟิกตอนดีพลอยคือจุดที่อยากเดาที่สุด และเดาแล้วได้ผลน้อยที่สุด ชื่อฟิลด์ดูเหมือนเดาได้ แต่บ่อยครั้งไม่ใช่ ในงานนี้มีฟิลด์หนึ่งที่ใส่ค่าดูสมเหตุสมผลมากลงไป แล้วโดนปฏิเสธ พอไปดึง JSON schema ที่แพลตฟอร์มเผยแพร่ไว้มาอ่าน ก็จบในการอ่านครั้งเดียว ฟิลด์นั้นต้องการชื่อ npm package ไม่ใช่ป้ายเวอร์ชัน คำตอบที่ถูกคือไม่ต้องใส่ฟิลด์นั้นเลย
Serverless functions read their configuration when the instance starts. Changing a variable without redeploying changes nothing. Set every variable before the first deploy, or the functions will boot and immediately throw on every request.
ฟังก์ชัน serverless อ่านค่าคอนฟิกตอนอินสแตนซ์เริ่มทำงาน เปลี่ยนค่าแล้วไม่ดีพลอยใหม่ เท่ากับไม่ได้เปลี่ยนอะไรเลย ตั้งค่าให้ครบ ก่อน ดีพลอยครั้งแรก ไม่งั้นฟังก์ชันจะบูตแล้วพังทุกคำขอทันที
Initialise the repository inside the project folder. Then prove your ignore rules work rather than trusting them:
สร้าง repository ในโฟลเดอร์โปรเจกต์ แล้วพิสูจน์ว่ากฎ ignore ทำงานจริง อย่าเชื่อไปเอง
git check-ignore -v .env.local node_modules/ .vercel git add -A git status --porcelain # read this list before committing
git check-ignore -v .env.local node_modules/ .vercel git add -A git status --porcelain # อ่านรายการนี้ก่อน commit ทุกครั้ง
If a real key ever does reach a commit: rotate the key first. Rewriting history does not help. Public repositories are scraped within minutes and the key is compromised from the moment it lands.
ถ้าคีย์จริงหลุดเข้า commit ไปแล้ว เปลี่ยนคีย์ก่อนเป็นอันดับแรก การเขียนประวัติใหม่ไม่ช่วยอะไร repository สาธารณะโดนกวาดข้อมูลภายในไม่กี่นาที คีย์นั้นถือว่าหลุดตั้งแต่วินาทีที่มันลงไป
Ship in the morning. The afternoon takes care of itself.
ปล่อยขึ้นจริงตอนเช้า ตอนบ่ายไม่ต้องจัดการอะไรแล้ว
Time, tokens and energy are the same currency spent three ways. Here is where they go.
เวลา โทเคน และแรง คือเงินสกุลเดียวกันที่จ่ายออกไปสามทาง นี่คือที่ที่มันหมดไป
| Expensiveแพง | Whyเพราะอะไร | ||
|---|---|---|---|
| Rebuilding after a wrong assumption | สร้างใหม่เพราะสมมติผิด | The whole output is discarded, and often the things built on top of it | ทิ้งผลลัพธ์ทั้งก้อน แถมของที่สร้างทับไว้ด้วย |
| Long back-and-forth about taste | โต้กันไปมาเรื่องรสนิยม | Every round re-sends the entire context and produces a fresh draft | ทุกรอบส่งบริบททั้งหมดใหม่ แล้วผลิตร่างใหม่ทั้งชุด |
| Debugging by guessing | แก้บั๊กด้วยการเดา | Each guess is a full cycle: change, deploy, wait, read, guess again | เดาหนึ่งครั้งคือหนึ่งรอบเต็ม แก้ ดีพลอย รอ อ่าน แล้วเดาใหม่ |
| Re-explaining the same standards | อธิบายมาตรฐานเดิมซ้ำ | Paid again in every single session | จ่ายใหม่ทุกครั้งที่เปิดงาน |
| Cheapถูก | Whyเพราะอะไร | ||
|---|---|---|---|
| Clarifying questions before building | ถามให้ชัดก่อนลงมือ | A few sentences that prevent a full rebuild | ไม่กี่ประโยค แลกกับการไม่ต้องสร้างใหม่ทั้งชุด |
| Pasting the real artifact | วางของจริงไปเลย | An image or an error carries more than a paragraph describing it | ภาพหนึ่งภาพหรือ error หนึ่งชุด บอกได้มากกว่าคำอธิบายทั้งย่อหน้า |
| Running the check in the same turn | ตรวจในรอบเดียวกัน | No context re-send; the model already has everything loaded | ไม่ต้องส่งบริบทใหม่ โมเดลถืออยู่ครบแล้ว |
| Loading standards once | ป้อนมาตรฐานครั้งเดียว | Every later output inherits them at no extra cost | ผลลัพธ์ทุกชิ้นหลังจากนั้นรับไปเอง ไม่มีค่าใช้จ่ายเพิ่ม |
Verifying everything is its own waste. The distinction that matters is whether the claim is checkable and whether being wrong is expensive.
ตรวจทุกอย่างก็เป็นการเปลืองอีกแบบ เส้นแบ่งที่สำคัญคือ สิ่งนั้น ตรวจได้ไหม และถ้าผิดแล้ว แพงไหม
| Always verifyต้องตรวจเสมอ | Usually trustปกติเชื่อได้ | ||
|---|---|---|---|
| Anything that moves money | อะไรก็ตามที่ทำให้เงินเคลื่อน | Layout and spacing — you can see it | เลย์เอาต์กับระยะห่าง เพราะมองเห็นเอง |
| Anything about who can read what | เรื่องใครอ่านอะไรได้บ้าง | Copy phrasing — you can read it | ถ้อยคำในคอปปี้ เพราะอ่านเองได้ |
| Config field names and values | ชื่อและค่าของฟิลด์คอนฟิก | Standard library usage | การใช้ไลบรารีมาตรฐาน |
| Statistics, dates, names, prices | สถิติ วันที่ ชื่อ ราคา | Common syntax | ไวยากรณ์ทั่วไป |
| Concurrency and ordering claims | ข้ออ้างเรื่องการทำงานพร้อมกันและลำดับ | Formatting conventions | ธรรมเนียมการจัดรูปแบบ |
Nobody down here is talking about tokens.
ตรงนี้ไม่มีใครคุยเรื่องโทเคนกันแล้ว
The shape of the actual session, reduced to its load-bearing moments. Adapt the words; keep the order.
โครงของการทำงานจริง เหลือไว้เฉพาะจังหวะที่รับน้ำหนัก ปรับคำได้ แต่รักษาลำดับไว้
Here's a screenshot of a site whose feel I want [attach]. Build a landing page for [product] aimed at [one reader]. Success is [one measurable outcome]. One HTML file, no framework, no build step. Before you start, ask me anything that would change what you build.
นี่คือภาพหน้าจอของเว็บที่ฉันอยากได้ฟีลแบบนี้ [แนบไฟล์] ทำหน้าแลนดิ้งให้ [สินค้า] สำหรับ [คนอ่านคนเดียว] ถือว่าสำเร็จเมื่อ [ผลลัพธ์ที่วัดได้หนึ่งอย่าง] ไฟล์ HTML เดียว ไม่ใช้เฟรมเวิร์ก ไม่มีขั้นตอน build ก่อนเริ่ม ถามมาก่อนถ้ามีอะไรที่จะเปลี่ยนสิ่งที่คุณสร้าง
Build the page. Then, in the same turn, confirm the tags balance, every internal link resolves to a file that exists, and every image has alt text. Show me the output of those checks.
ทำหน้านี้ แล้วในรอบเดียวกันนั้น ยืนยันว่าแท็กเปิดปิดครบ ลิงก์ภายในทุกอันไปถึงไฟล์ที่มีอยู่จริง และรูปทุกรูปมี alt text แล้วโชว์ผลการตรวจให้ดูด้วย
Design the schema. The rule that must never break is [invariant]. Enforce it with a database constraint, not application logic. Then run the migrations against a real database and show me it works.
ออกแบบโครงฐานข้อมูล กฎที่ห้ามพังเด็ดขาดคือ [กฎ] บังคับด้วย constraint ในฐานข้อมูล ไม่ใช่ตรรกะฝั่งแอป แล้วรัน migration กับฐานข้อมูลจริง แล้วให้เห็นว่ามันทำงาน
Write the tests that try to break the [invariant], including one that bypasses the application layer entirely. Run them. Tell me plainly which guarantees are tested and which are reasoned about.
เขียนเทสต์ที่พยายามทำให้ [กฎ] พัง รวมถึงตัวที่ข้ามชั้นแอปไปเลย รันให้ดู แล้วบอกตรง ๆ ว่าข้อรับประกันไหนถูกเทสต์จริง ข้อไหนแค่ให้เหตุผลไว้เฉย ๆ
Wire up checkout. The price must come from the database, never the request body. Confirm payment only in the signed webhook, verified against the raw body, and make it idempotent. Then test: send a forged amount, and replay the same event twice.
ต่อระบบ checkout ราคาต้องมาจากฐานข้อมูล ห้ามมาจาก request body ยืนยันการจ่ายเงินใน webhook ที่ตรวจลายเซ็นกับ raw body เท่านั้น และทำให้รันซ้ำแล้วไม่พัง แล้วเทสต์ ส่งจำนวนเงินปลอมมาหนึ่งครั้ง แล้วยิง event เดิมซ้ำอีกรอบ
Prepare it for deployment. Validate the config against the platform's published schema rather than assuming field names. List every placeholder, stub and invented fact still in the project.
เตรียมสำหรับดีพลอย ตรวจคอนฟิกกับ schema ที่แพลตฟอร์มเผยแพร่ไว้ อย่าเดาชื่อฟิลด์เอง แล้วลิสต์ทุกอย่างที่ยังค้างอยู่ในโปรเจกต์ ทั้งของหลอก stub และข้อมูลที่แต่งขึ้น
Asking for the list of what is still fake, at the end of every session, is the single highest-value habit in this document. It costs one sentence. It is the difference between a demo and a product.
การขอรายการว่ายังมีอะไรปลอมอยู่บ้าง ตอนจบทุกรอบการทำงาน คือนิสัยที่คุ้มที่สุดในเอกสารทั้งฉบับ ราคาแค่หนึ่งประโยค และมันคือเส้นแบ่งระหว่างเดโมกับสินค้าจริง
| □ | Four-line brief written down before the first prompt | เขียนบรีฟสี่บรรทัดก่อนพร้อมต์แรก |
| □ | Standards loaded at the start of the session | ป้อนมาตรฐานตั้งแต่เริ่มรอบ |
| □ | Real reference material pasted, not described | วางของอ้างอิงจริง ไม่ใช่อธิบายเอา |
| □ | Every build request carries its verification | ทุกคำสั่งสร้างมีคำสั่งตรวจติดไปด้วย |
| □ | Invariants enforced by database constraints | บังคับกฎที่ห้ามผิดด้วย constraint ในฐานข้อมูล |
| □ | Prices read server-side, never from the request | ราคาอ่านฝั่งเซิร์ฟเวอร์ ไม่เอาจากคำขอ |
| □ | Payment confirmed only in the signed webhook | ยืนยันการจ่ายเงินใน webhook ที่มีลายเซ็นเท่านั้น |
| □ | Duplicate events proven harmless by test | พิสูจน์ด้วยเทสต์ว่า event ซ้ำไม่ทำให้พัง |
| □ | Public key confirmed unable to read private tables | ยืนยันว่า public key อ่านตารางส่วนตัวไม่ได้ |
| □ | Ignore rules tested with a planted fake secret | ทดสอบกฎ ignore ด้วยการวางคีย์ปลอมไว้จริง |
| □ | Config validated against the published schema | ตรวจคอนฟิกกับ schema ที่เผยแพร่ไว้ |
| □ | Every placeholder and invented fact written down | จดของหลอกและข้อมูลที่แต่งขึ้นไว้ครบทุกจุด |
Bring the project you keep not finishing. Leave with it live.
เอาโปรเจกต์ที่ทำไม่จบสักทีมา แล้วกลับไปพร้อมของที่ขึ้นจริงแล้ว