Panupong S.
← All work
~/work/ployfreshfruit

Ployfreshfruit

A fruit shop that sold almost everything through its Facebook page, and wanted a website of its own. Three of us built it. I did the admin dashboard, the product and admin APIs, everything on LINE, and the whole deploy pipeline.

role
Full-stack developer & DevOps
team
Me + 2 developers
when
May – Sep 2026
stack
Next.js · Express (TS) · Prisma · PostgreSQL · Docker
StorefrontLINE Rich MenuAdmin dashboard
01 / context

Before the website

Ploy Fresh Fruit sells imported fruit and gift baskets. Most of their customers found them on their Facebook page and ordered there. They wanted a website of their own as one more way to sell, not a replacement for Facebook.

So now there are two sides: ployfreshfruit.com for customers, and LINE for the shop, where new orders show up and most of the daily admin work gets done.

02 / my part

What I built

There were three of us. These are the parts that were mine. A few dashboard pages use APIs my teammates wrote, and I built those pages and wired them up. I've done full-stack work on a lot of projects, so on this one I pushed myself on the DevOps side.

  • frontend

    Admin dashboard

    • Overview + analytics
    • Order management
    • Product management
    • User management
  • backend

    APIs

    • Baskets CRUD
    • Fruits CRUD
    • Product & basket builder
    • Admin overview APIs
    • All image uploads to Cloudinary
  • integration

    LINE

    • Messaging API, end to end
    • New order alerts
    • Rich Menu UI design
    • Slip approval, prep and shipping flows
  • devops

    Deploy

    • GitHub Actions build & push
    • SSH deploy with Docker Compose
    • Nginx + Certbot HTTPS for the domain
03 / system architecture

How the pieces fit

System architecture: browser to Nginx on a DigitalOcean droplet, Docker Compose running frontend, backend and PostgreSQL, with Cloudinary and LINE Messaging API outside
System architecture · click to open full size ↗
Edge
Nginx reverse proxy on a DigitalOcean droplet, with HTTPS certificates from Let's Encrypt via Certbot.
Frontend
Next.js, TypeScript and Tailwind CSS. Serves the storefront and the admin dashboard.
Backend
Express.js with TypeScript and Prisma ORM. All business logic, admin APIs and the LINE webhook.
Database
PostgreSQL, running in the same Docker Compose stack.
Outside services
Cloudinary for product images and payment slips, LINE Messaging API for the owner's Rich Menu, ThaiBulkSMS for OTP phone login.
04 / backend

The hardest part: baskets

This is the part that took the most thinking. A basket isn't one product with one price. Every set, like a birthday basket, has its own rules, and the same fruit can cost a different amount depending on which set it's in. I designed the Product APIs around that:

Set price
Each set has its own price.
Fruit choices
How many kinds of fruit the customer can pick, which fruits are allowed, and what each one costs in this set.
Basket choices
Which baskets the customer can choose for that set.
Images
Every image in the system is uploaded through the backend to Cloudinary, so the logic lives in one place.

Around it sit the Baskets and Fruits CRUD APIs, and the admin APIs that summarise sales for the overview page.

05 / integration

Why LINE, not just a dashboard

This was my idea. Opening a web dashboard every time isn't always convenient, and when a customer orders, the shop needs to know right away. So new orders send a message straight to LINE, and the everyday admin work happens from the Rich Menu. I built the whole LINE side and designed the menu.

LINE Official Account with the Ploy Fresh Fruit Rich Menu
LINE OA · Rich Menu
New order alerts
A LINE message as soon as a customer places an order.
Check & approve slips
Payment slips show up in LINE, and the owner approves or rejects them from the Rich Menu.
Orders to prepare
See what still needs preparing, and mark an order as prepared once it's packed.
Orders to ship
See every order that hasn't gone out yet.
06 / frontend

The admin dashboard

For anything bigger than a quick check, there's the web dashboard. It's built for a phone first.

Admin product list with open and closed products and edit buttons
Admin · product management
Overview
Sales summary and analytics, backed by the admin APIs.
Orders
Track every order and its status.
Products
Create sets, open or close them for sale, edit prices.
Users
Manage customer and admin accounts.
07 / devops

The part I'm proudest of

CI/CD: developer pushes to main, GitHub Actions builds frontend and backend images, pushes to Docker Hub, then deploys over SSH to the droplet
CI/CD pipeline · click to open full size ↗

I set up the whole pipeline by myself, and it failed a lot of times before it worked. Now every push to main builds both images, pushes them to Docker Hub, and deploys them to the droplet over SSH. Nobody logs in to the server by hand. Nginx and Certbot sit in front and keep HTTPS working for the domain.

08 / decisions

Choices and what they cost

  • LINE as the admin interface

    whyOpening the dashboard isn't always convenient, and new orders need to be seen right away.

    costA Rich Menu is a small screen with limited UI, and the workflow depends on LINE's API.

  • One droplet with Docker Compose

    whyCheap, simple, and the whole stack starts with one command.

    costEverything runs on one machine, so there's no failover if it goes down.

  • Deploy over SSH from GitHub Actions

    whyStraightforward to set up and easy to read.

    costThe pipeline holds a key to the production server, so it has to be kept tightly scoped.

09 / lessons

What I'd do differently

  • Ship with a way back.

    The deploy stops the old containers before starting the new ones, so a bad release keeps the site down until I fix it by hand. Next time I'd start the new version first, health-check it, and roll back to the previous image automatically if the check fails.

  • Run migrations before switching traffic.

    Right now the database migration runs after the new server is already live, so for a short window new code talks to the old schema. I'd make the migration its own step that runs first and stops the deploy if it fails.

  • Write tests for the order flow early.

    I set up Vitest but never wrote the tests. The order and payment status flow (verify → receive → prepare → ship) is where a bug costs real money, so that's what I'd cover first, with a lint and type-check job that has to pass before any image is built.

10 / results

Where it is now

Catalog
30 products, 28 open for sale (Nov 2026)