Skip to content

andre-christi-kan, portfolio

Freelance

Korner

  • iOS
  • Android
  • Fullstack Engineer

Legal tech platform that connects people with lawyers for 30-minute live consultations, booked and paid in the app. Korner connects clients in Indonesia with lawyers for paid 30-minute live consultations. A client picks an approved lawyer and a slot, describes the case in writing and in a voice note, and pays online. The chat room for a slot opens 5 minutes before it starts and closes itself when the time is up. I was the only developer, hired as a freelance consultant. I recommended the stack and the hosted services. The client decided. The backend started from my own ack-nestjs-boilerplate. I built the NestJS API, the Next.js admin dashboard, and the Flutter app for iOS and Android. The Flutter app shipped to the App Store and Google Play.

Where
Freelance project for Korner, not employment
Role
Fullstack Engineer, the only developer. I built the NestJS API, the Next.js admin dashboard, and the Flutter app for iOS and Android.
Dates
Oct 2023 – Apr 2024
Platforms
iOS and Android (App Store and Google Play)
Stack
NestJS 10, TypeScript 5.4, MongoDB, Flutter 3.16, Next.js 14, Twilio, Xendit, AWS
per consultation
30 min
close paths
3
invoice expiry
15 min
iOS and Android from one Flutter app
Cross-platform

Infra

Infra around KornerThe Korner REST API, NestJS in Docker, in the centre. The Flutter app for iOS and Android calls it over REST. The Next.js admin dashboard calls it over REST. The Flutter app talks to Twilio Conversations directly for live chat. The API creates rooms and tokens on Twilio Conversations, and Twilio Conversations sends close and message webhooks back. The API stores data in MongoDB and voice notes and documents in S3. It sends invoices and payouts to Xendit, which sends a payment webhook back. It sends OTP codes, reminders and new-message alerts through OneSignal push, Twilio WhatsApp and AWS SES email.RESTRESTlive chatrooms, tokens /close, message webhooksvoice notes,documentsinvoices, payouts /payment webhookOTP, reminders,new messagesFlutter appiOS and AndroidNext.js admin dashboardKorner REST APINestJS in DockerMongoDBObject storageS3Twilio ConversationsXenditMessagingOneSignal, TwilioWhatsApp,AWS SES
The Flutter app (iOS, Android) and the Next.js admin dashboard call the Korner REST API, NestJS in Docker. The Flutter app talks live chat with Twilio Conversations. The API talks to Twilio Conversations for rooms, tokens, and close and message webhooks, to MongoDB, to object storage on S3 for voice notes and documents, to Xendit for invoices, payouts and the payment webhook, and to messaging: OneSignal push, Twilio WhatsApp, AWS SES. The Next.js admin is a part of Korner. Twilio Conversations, Xendit and the messaging providers are outside providers: dashed boxes.

01Context and my role

I was the only developer, hired as a freelance consultant. I coordinated the project directly with the client. I recommended the stack and the hosted services. The client decided. The backend started from my own ack-nestjs-boilerplate. The Flutter app shipped to the App Store and Google Play.

02What it does

  • Lawyer publish

    A lawyer submits a profile, ID card and credentials. An admin sets publish to on review, approve, reject, inactive or cancel. Only approve can be booked.

  • Schedules

    Lawyers set weekly availability in 30-minute slots between 08:00 and 20:00 on lawyer-schedule.

  • Booking

    A client books up to 30 days ahead with a written description, an optional voice note of up to 5 minutes and up to 3 PDF documents. A slot cannot be double-booked, and a client can hold at most 5 unpaid bookings at once.

  • Payment

    A Xendit invoice per booking that expires after 15 minutes. The app opens Xendit's hosted invoice page. Lawyers are paid out through Xendit.

  • Consultation room

    Real-time text chat between client and lawyer, a countdown in the app and an alert 10 minutes before the end. The voice note and documents are playable and viewable in the room.

  • Reminders

    WhatsApp 15 minutes before the session and push 5 minutes before.

  • After the session

    The client leaves a review.

  • Account

    Sign-in by password, Google, Apple or WhatsApp OTP, password reset, terms (tnc) and privacy policy.

  • Admin dashboard

    A Next.js admin that approves lawyers, reviews lawyers, and lists transactions.

  • Language

    English and Indonesian.

03How it works

Booking and payment

  • Creating a booking is one MongoDB transaction: the consultation record, the S3 uploads and the Xendit invoice succeed or fail together.
  • Status flow: waiting for payment, then reserve, then completed, or cancel by client, or canceled.
  • The Xendit webhook is checked with a signature before it changes anything. A paid webhook sets reserve and creates the chat room.

A session that closes itself, in three layers

  • Chat service timer: when the room is created, the conversation on Twilio Conversations gets a close timer equal to the minutes left until the slot ends.
  • Webhook: when the chat service closes the conversation, its webhook marks the room closed and the consultation completed, inside a transaction.
  • Cron sweep: a NestJS cron job runs every 10 minutes and closes any expired consultation the webhook missed. An environment flag can switch it off.
  • Users can open the room only from 5 minutes before the start until the end.

As client

As client, from booking to reviewBooking and payment: the client picks a lawyer and a slot in the client app and uploads the voice note and documents. The Korner API creates a Xendit invoice, and the booking waits for payment. Xendit's payment webhook, signature checked, then reserve, then a chat room with a close timer on Twilio Conversations. Before and during the session: a WhatsApp reminder at 15 minutes before and a push at 5 minutes before. The app can open the room from 5 minutes before the start. Live chat shows a countdown with an alert 10 minutes before the end. Closing: first the chat service timer closes the room, then its close webhook records it, and the API marks the room closed and the consultation completed. Third, if the webhook was missed, a cron sweep every 10 minutes completes it. Then the client leaves a review.WhatsApp,pushTwilioConversationsXenditKorner APIClient appBooking and paymentwaiting forpaymentreserveBefore and during the sessioncountdown in the app.Alert 10 min before the endClosing1. timer closesthe roomroom closed.Consultation completed3. cron sweep every10 min if the webhookwas missedAfter the sessionlawyer, slot,voice note, documentsinvoicepayment webhook(signature checked)chat room, close timerWhatsApp −15 min,push −5 minopen roomfrom −5 minlive chat2. close webhookclient leaves a review
Booking and payment: client app to API (lawyer, slot, voice note, documents) to Xendit invoice, waiting for payment, then payment webhook (signature checked), then reserve, then a chat room with a close timer on Twilio Conversations. Before and during the session: WhatsApp reminder at −15 min, push at −5 min, the app opens the room from −5 min, live chat, countdown in the app, alert 10 min before the end. Closing: 1. chat service timer closes the room. 2. close webhook, room closed, consultation completed. 3. cron sweep every 10 min if the webhook was missed. After the session: the client leaves a review.

Voice notes and files

  • Recorded in the app as AAC (m4a) with a waveform, validated on the server for format and declared duration, stored on S3 under randomised paths, and linked to the consultation.

Chat

  • The backend creates one conversation per consultation on Twilio Conversations, adds the lawyer and the client, and issues their access tokens. The app connects with the Twilio Conversations SDK. New messages trigger a push notification through the webhook.

Structure

  • 19 domain modules (consultation, district, email, lawyer-schedule, privacy-policy, privacy-policy-history, province, publish, push-notification, regency, reset-password, role, setting, tnc, tnc-history, user, verification, village, whatsapp) on 15 shared modules from the boilerplate (API key, auth, AWS, database, logging, docs, errors, files, helpers, i18n, pagination, policy, request, response, health).
  • 4 roles (super admin, admin, lawyer, client). Each module has separate controllers for client, lawyer, admin and incoming webhooks, and CASL policies check every route.
  • Mobile app: MVVM with Provider view models and get_it for dependency injection, 16 feature areas (administrative, agreement, auth, chat, consultation, home, publish, reset_password, review, room, schedule, setting, sign_up, splash, user, verification).

As lawyer

As lawyer, from publish to payoutThe lawyer sends a profile, ID card and credentials from the lawyer app to the Korner API. Publish sits on review until the admin approves. The lawyer then sets lawyer-schedule slots. Reminders go out, the lawyer opens the room from 5 minutes before the start, and live chat runs on Twilio Conversations until the close timer. After the consultation is completed, the API sends a payout through Xendit.WhatsApp,pushXenditTwilioConversationsAdminKorner APILawyer appPublish and schedulepublish onreviewSessionPayoutconsultationcompletedprofile, ID card,credentialsadmin approvelawyer-schedule slotsremindersopen roomfrom −5 minlive chat untilthe close timerpayout
Publish and schedule: lawyer app to API (profile, ID card, credentials), publish on review, admin approve, lawyer-schedule slots. Session: reminders, lawyer opens the room from −5 min, live chat on Twilio Conversations until the close timer. Payout: API to Xendit payout after the consultation is completed.

04Decisions and trade-offs

Engagement

Next.js admin, three actions

Why
I recommended Next.js because I could use an existing starter and build the dashboard quickly. The surface is approve lawyer, review lawyer, and list transactions.

One Flutter app for both stores

Why
I recommended Flutter so one codebase covers iOS and Android. The app shipped to the App Store and Google Play.

Chat and payments

Hosted chat on Twilio Conversations

Why
I recommended hosted chat because building chat from scratch takes time and the client's timeline was short. The client chose Twilio Conversations. The API only manages rooms and tokens.

Xendit invoices and payouts

Why
I recommended Xendit so admin does not have to record transactions or transfer money by hand. Lawyers can take payouts themselves. The client chose Xendit.

Reminders

Why
I recommended reminders so the client is ready before the slot starts. WhatsApp is one path. Email and push carry them too.
Cost
Each WhatsApp message is billed. Email and push are not billed per transaction and have large quotas.

05Testing and delivery

  • Tests: 18 Jest unit test files on the shared services from my ack-nestjs-boilerplate, with a 100% coverage threshold on those services. The Korner domain modules have no automated tests.
  • CI/CD (GitHub Actions): on main and staging, a multi-architecture Docker Buildx image is pushed to AWS ECR, then an SSH step pulls it on the server and restarts the container. Production and staging have separate jobs. Lint and test workflows on Node.js LTS, and a release workflow that tags a GitHub release.
  • Local quality gates: Husky, ESLint, Prettier, cspell, a dead-code check.
  • Operations: data seeding and migrations from a command-line tool, Swagger docs, health checks, Sentry, rotating Winston logs.

06Stack

Backend
  • Node.js 20
  • TypeScript 5.4
  • NestJS 10
  • Mongoose 8 (MongoDB, transactions)
  • NestJS Schedule (cron)
  • Swagger
  • NestJS i18n
Auth and access
  • Passport JWT (access and refresh)
  • Google sign-in
  • Apple sign-in
  • CASL
Chat and messaging
  • Twilio Conversations
  • Twilio WhatsApp
  • OneSignal (push)
  • AWS SES (email)
Payments
  • Xendit invoices, webhooks and payouts
Files
  • AWS S3
Security and ops
  • NestJS Throttler
  • Sentry
Mobile
  • Flutter 3.16
  • Dart 3.2
  • Sentry
Web dashboard
  • Next.js 14
Testing
  • Jest
Delivery
  • Docker (multi-arch Buildx)
  • GitHub Actions
  • AWS ECR