I modernise the systems businesses can’t afford to break.
I replace legacy dependencies, migrate live data and connect platforms that were never designed to talk to each other. At Linfox my main project has been redesigning the customer portal, with React, TypeScript, Node.js, PostgreSQL and AWS.
Intermediate API removed. Sign-in over 50% faster. 10,000+ records moved, none lost.
Simplified sign-in architecture from my Linfox work, drawn for this portfolio.
Selected work
Linfox2025–presentPortal redesign, part 1
Replacing Dataverse without losing anyone’s access
The first project I took on at Linfox was redesigning the customer portal, starting with a new authorisation model. I rebuilt authentication and authorisation around PostgreSQL on AWS RDS. Every existing user had to keep the same sign-in and the same access to customer records on the day we switched.
10,000+ records migrated with zero data loss and no change to how users sign in.
Sign-in over 50% faster on average for 3,000+ users after removing the intermediate API layer. Sign-in API calls dropped from about 7–8 seconds to 3–4.
Users can be set up directly in the portal, with no matching record needed in Dataverse.
How the data model works
I moved user, account and permission data into PostgreSQL with Prisma. A junction table links each user to the accounts they can see and what they can do there, so internal staff can work across many customers while external users stay scoped to one.
The authorisation rules themselves didn’t change. Only where the data lives did, which kept the switch low-risk and easy to verify.
Linfox2025–presentPortal redesign, part 2
Orders that go from the portal straight into the TMS
The other half of the redesign: customers create an order in the portal and it is created in Blue Yonder, the transport management system. I worked with the portal team on the ordering interface, the status that comes back once the order exists, and what customers see when it fails.
1
Customer creates the order in the portal
2
Portal validates and submits it
3
Integration layer forwards itBuilt and run by another team.
4
Order created in Blue YonderStatus and any errors shown back in the portal.
Solid line: portal team, my workDashed line: another team
Ordering interface. Order creation in the portal, with request payloads validated by Zod before they leave it.
Status back in the portal. Once Blue Yonder has the order, its status shows where the customer created it.
Failures explained. When an order is rejected downstream, the portal shows why and what to change.
Linfox2026Prototype
An order assistant that explains failures instead of guessing
I’m prototyping an assistant for the customer portal that drafts new orders from a user’s history and explains why an order failed and how to fix it.
Data on demand. A Lambda function queries order data when it’s needed and passes it to a Snowflake Cortex agent, avoiding the cost of syncing everything into Snowflake.
Same access rules as the portal. The assistant reuses the existing visibility logic, so it can only see orders the user can already see.
Predictable answers. Known error types map to pre-written explanations and fixes, rather than asking the model to interpret raw system errors.
Linfox2025–present
An internal tool for master data changes
Updating service IDs and locations used to take a developer up to an hour per request. I built a role-restricted internal tool so the development team can make the change in under five minutes.
Previous process30–60 min
Internal toolunder 5 min
015304560 min
Related work: faster data tables
I also rebuilt the portal’s data-heavy tables, moving from Material UI Table to AG Grid with sorting and column reordering. TanStack Query caching cut repeat requests when switching pages.
Independent projectE-commerce operations
Automating order handoff to a shipping provider
I built and run a TypeScript service for a small cross-border e-commerce business. It turns Shopify orders into YunExpress shipments, with a dashboard for the people packing them. It processes 100+ orders a month.
1
Pick up new Shopify ordersOn a schedule, from a saved cursor.
2
Choose the route and build customs dataFrom validated rules, not code.
3
Create the YunExpress waybillSigned API request.
4
Operator reviews and marks it fulfilledDeliberately manual, so a person decides when a customer is told it shipped.
Solid line: automatedDashed line: done by a person
No duplicate waybills. Manual and scheduled runs once created two waybills for one order. Now the Shopify order name is YunExpress’s customer order number, so YunExpress itself rejects a second submission, on top of per-order state in SQLite.
Failures wait for a fix, not endless retries. After three attempts an order is parked until someone edits it in Shopify, for example adding a missing phone number, which queues it again automatically.
Shipping rules as data. Routes are picked by country, state and the checkout shipping method. Customs lines are merged by product category, converted to USD, capped at what the customer paid, and include IOSS for EU orders.
How it runs
Node.js and TypeScript, with SQLite for state and Express for the dashboard, which also prints labels and packing slips, books pickups and syncs actual shipping costs back to Shopify. A daily summary goes to WeChat. 90+ Vitest tests run with every network call mocked.
It runs on a DigitalOcean VPS with PM2 and Caddy, deployed by GitHub Actions.
Company work is described without internal code, customer names or system screenshots. Diagrams are simplified.
Experience
Linfox
Full Stack Developer
Redesigning the customer portal: a new PostgreSQL-based authorisation model, and ordering that creates orders in Blue Yonder. Also set up local development with Serverless Offline and moved bundling to esbuild for faster builds.
Auxita
Software Engineer
Built healthcare platform features, prescriber tools and patient enrolment workflows with React, Node.js and MongoDB.
Tecks
Full-stack Developer
Built e-commerce and golf booking workflows for a sports retail client.
About
I’m Tianhao, also known as Ethan. I’m based in Melbourne and have worked across logistics, healthcare and e-commerce.
Before my Master of Science in Information Technology and Systems at the University of Tasmania, I studied Russian Language and Literature at Beijing International Studies University. I speak Mandarin natively and Russian fluently.
I’m looking for a senior individual-contributor role on a product team, where I can own features end to end and help make systems simpler to change.