Skip to content

Repository files navigation

Тестовое на вакансию Fullstack Node.js Developer

A minimal fullstack user activity tracker inspired by Google Analytics, built with Node.js, TypeScript, and MongoDB.

🧠 Overview

This service tracks user events on static HTML pages, buffers them in the browser, and sends them to a backend for storage and analysis.

✅ Project Architecture

📦 Vite (port 50000) — Frontend Dev Server

  • Serves: 1.html, 2.html, 3.html
  • Used as a static HTML server during development
  • Also compiles tracker.ts into a UMD bundle (dist/tracker.umd.js)

⚠️ Does not serve /tracker in production — that responsibility is delegated to the backend

🚀 Fastify (port 8888) — Backend Server

  • Serves: GET /tracker → returns the compiled tracker.umd.js from Vite’s dist/ directory
  • Receives: POST /track → accepts analytics events sent from the client
  • Stores: Analytics events in MongoDB using bulk insert

⚙️ May also provide CORS headers, compression, and other production optimizations

🧪 Development Scenarios

1. Tracker Development (Client only)

yarn client:dev

2. Backend Development

yarn db:up              # For database by Docker (optional)
yarn server:dev

3. Full Integration Flow (Tracker + Backend)

yarn db:up              # For database by Docker (optional)
yarn client:build       # Build both client and tracker
yarn server:start       # Start backend to serve tracker and handle events
yarn client:dev         # Serve static HTML pages
  • Open a test page like: http://localhost:50000/1.html
  • The page loads tracker from: http://localhost:8888/tracker
  • Events are sent to: http://localhost:8888/track
  • ✅ Analytics pipeline fully tested end-to-end

📦 Backend API

POST /track

Receives an array of events and stores them in MongoDB. Events are validated before storing.

[
  {
    "event": "click-button",
    "tags": ["some", "optional", "tags"],
    "url": "http://localhost:50000/1.html",
    "title": "My Website",
    "ts": 1675209600
  }
]
  • ✅ Valid input → 200 OK
  • ❌ Invalid input → 422 Unprocessable Entity

GET /tracker

Returns the minified JS tracker script that will be injected in each page.

✳️ Bonus Features

  • ✅ Custom snippet for tracker injection (like Google Analytics)
  • ✅ Ensures events are sent before link navigation
  • ✅ Avoids preflight CORS requests by tweaking headers

🧪 Tech Stack

  • Backend: Node.js + Fastify
  • DB: MongoDB
  • Language: TypeScript (both client & server)
  • Formatter: Prettier, Linter

🛠 Work Plan

Stage 1: Project Setup ⭐️

  • Init project structure (client/, server/, pages/)
  • Add TypeScript, tsconfig, Lint, Prettier
  • Configure precommit by Husky
  • Setup MongoDB connection (docker-compose)

Stage 2: Static HTML Pages ⭐️

  • Create 1.html, 2.html, 3.html in /pages
  • Serve pages on port 50000
  • Include tracker via <script src="...">

Stage 3: JS Tracker ⭐️

  • Create global tracker object
  • Implement .track(event, ...tags)
  • Append url, title, ts to each event
  • Buffer events
  • Flush if 3+ events
  • Flush if 1s passed
  • Flush on unload
  • Retry on failure

Stage 4: Backend API ⭐️

  • Setup Fastify server on port 8888
  • Add /tracker route to serve JS file
  • Add /track route to accept event array
  • Add /event route to get event array
  • Add CORS
  • Validate input format via Zod
  • Respond 200 OK or 422 Unprocessable
  • Insert events to MongoDB (bulk insert)
  • Respond without waiting for DB write

Stage 5: Bonus Features ✳️

  • Create snippet for async script injection
  • Prevent navigation until flush completes (Beacon API)
  • Avoid preflight request (CORS optimizations)

Stage 6: Finalization ⭐️

  • Write README.md with instructions
  • Add log output for debugging
  • Test in Chrome
  • Add summary of time spent and challenges in PR description

⏱ Time Spent

Total: ~4-6 hours

Решил комбинировать 2 вида запросов: fetch & beacon

  • fetch в обычно ситуации чтобы знать о проблемах с сетью и иметь возможность выполнить ретраи
  • beacon в случае ухода со страницы для гарантированной отправки

Основная сложность: Сложнее всего было точно выяснить требования в условиях нарушенной коммуникации с заказчиком. Это требовало самостоятельно принимать решения и выявлять логические несостыковки в условиях. Я рассматривал это как часть задания и старался поднимать подобные вопросы заранее.

Касательно сниппета: главное преимущество в том, что мы

  • не блокируем загрузку основной страницы
  • при этом уже собираем статистику до загрузки скрипта

Мои замечания по условиям:

  1. Если во время ожидания секунды после сбоя сети в буфер придет 3 новых события, они будут немедленно отправлены на сервер? Если да:
  • Мы нарушаем порядок отправки событий. Новые события уходят раньше тех, которые уже были в буфере.
  • Мы предполагаем, что проблема в сети, а не в самих событиях. Зачем повторно делать запрос раньше таймаута, если мы только что убедились, что соединение не работает?
  • В результате мы делаем лишние запросы при заведомо плохой сети, что противоречит цели — минимизировать их количество.

Было бы логичнее: после неудачной попытки замораживать отправку на 1 секунду полностью (независимо от количества событий в буфере), и только после таймаута пробовать снова. Исключение — если пользователь уходит со страницы.

  1. Если мы получаем на сервер 10 событий, 9 из которых валидно, а 1 нет - то мы отвечаем 422 и не принимаем ни одно событие то есть мы просто теряем 9 валидных сбытий. Возможно, было бы логичнее обрабатывать валидные события и возвращать мультистатус 207

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages