This document provides step-by-step instructions for implementing the remaining improvements from the project assessment.
The first three improvement areas have been completed and applied to serverless API routes:
- ✅ Security Concerns (validation, rate limiting, environment variables)
- ✅ Error Handling (standardized errors, logging)
- ✅ Code Quality (types, constants, removed console.logs)
All improvements are now in the serverless architecture (src/lib/server/ and src/routes/api/).
This guide covers the remaining improvements (4-10) that need configuration and manual setup.
# Frontend tests
npm install -D vitest @testing-library/svelte @testing-library/jest-dom jsdom
npm install -D @testing-library/user-event happy-dom
# Backend tests
cd backend
npm install -D jest @types/jest ts-jest supertest @types/supertestCreate vitest.config.ts in the root:
import { defineConfig } from 'vitest/config';
import { sveltekit } from '@sveltejs/kit/vite';
import path from 'path';
export default defineConfig({
plugins: [sveltekit()],
test: {
include: ['src/**/*.{test,spec}.{js,ts}'],
environment: 'jsdom',
globals: true,
setupFiles: ['./src/test/setup.ts']
},
resolve: {
alias: {
$lib: path.resolve(__dirname, './src/lib')
}
}
});Create src/test/setup.ts:
import '@testing-library/jest-dom';
import { vi } from 'vitest';
// Mock environment variables
vi.mock('$env/static/public', () => ({
PUBLIC_SUPABASE_URL: 'http://localhost:54321',
PUBLIC_SUPABASE_ANON_KEY: 'test-key'
}));Create src/lib/server/__tests__/ directory and add tests for serverless utilities:
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/__tests__/**/*.test.js', '**/?(*.)+(spec|test).js'],
setupFilesAfterEnv: ['<rootDir>/jest.setup.js']
};Create backend/jest.setup.js:
// Mock environment variables
process.env.SUPABASE_URL = 'http://localhost:54321';
process.env.SUPABASE_ANON_KEY = 'test-key';
process.env.TELEGRAM_BOT_TOKEN = 'test-token';In package.json (root):
{
"scripts": {
"test": "vitest",
"test:ui": "vitest --ui",
"test:coverage": "vitest --coverage"
}
}In backend/package.json:
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage"
}
}Create src/lib/stores/__tests__/auth.test.ts:
import { describe, it, expect, beforeEach } from 'vitest';
import { get } from 'svelte/store';
import { isAuthenticated, authUser, logout, loginDemo } from '../auth';
describe('auth store', () => {
beforeEach(() => {
logout();
});
it('should set authenticated state', () => {
loginDemo({ username: 'test' });
expect(get(isAuthenticated)).toBe(true);
expect(get(authUser)).toEqual({ username: 'test' });
});
it('should logout', () => {
loginDemo({ username: 'test' });
logout();
expect(get(isAuthenticated)).toBe(false);
expect(get(authUser)).toBe(null);
});
});Create backend/routes/__tests__/exams.test.js:
const request = require('supertest');
const express = require('express');
const examRoutes = require('../exams');
const app = express();
app.use(express.json());
app.use('/exams', examRoutes);
describe('GET /exams', () => {
it('should return 400 for invalid courseId', async () => {
const res = await request(app).get('/exams').query({ courseId: 'invalid-uuid' });
expect(res.status).toBe(400);
expect(res.body).toHaveProperty('ok', false);
});
});npm run build
npx vite-bundle-visualizerUpdate vite.config.ts:
export default defineConfig({
plugins: [tailwindcss(), sveltekit()],
build: {
rollupOptions: {
output: {
manualChunks: {
'pdf-viewer': ['@pdftron/pdfjs-express-viewer'],
supabase: ['@supabase/supabase-js'],
telegram: ['telegraf']
}
}
}
}
});npm install -D @sveltejs/enhanced-img sharpReplace <img> with optimized components:
<script>
import { EnhancedImage } from '@sveltejs/enhanced-img';
</script>
<EnhancedImage
src="/images/thumbnail.jpg"
alt="Course thumbnail"
width={400}
height={300}
loading="lazy"
/>For serverless functions, add cache headers directly in API routes:
export function cacheControl(maxAge = 3600) {
return (req, res, next) => {
res.set('Cache-Control', `public, max-age=${maxAge}, immutable`);
next();
};
}Apply to static routes:
router.get('/courses', cacheControl(3600), asyncHandler(handler));cd backend
npm install swagger-jsdoc swagger-ui-express
npm install -D @types/swagger-jsdoc @types/swagger-ui-expressCreate backend/config/swagger.js:
import swaggerJsdoc from 'swagger-jsdoc';
const options = {
definition: {
openapi: '3.0.0',
info: {
title: 'FreshHub API',
version: '1.0.0',
description: 'API documentation for FreshHub learning platform'
},
servers: [
{
url: 'http://localhost:4000',
description: 'Development server'
}
]
},
apis: ['./routes/**/*.js', './index.js']
};
export const swaggerSpec = swaggerJsdoc(options);In backend/index.js:
import swaggerUi from 'swagger-ui-express';
import { swaggerSpec } from './config/swagger.js';
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));Example in backend/routes/exams.js:
/**
* @swagger
* /api/exams:
* get:
* summary: Get exams by course ID
* tags: [Exams]
* parameters:
* - in: query
* name: courseId
* required: true
* schema:
* type: string
* format: uuid
* responses:
* 200:
* description: List of exams
*/
export const GET: RequestHandler = ...;Add JSDoc comments to components:
<!--
@component ResourceCard
@description Displays a resource card with thumbnail and metadata
@prop {Resource} resource - The resource data to display
@prop {Function} onSelect - Callback when resource is selected
-->
<script>
export let resource;
export let onSelect;
</script>Create backend/middleware/index.js:
export { authMiddleware } from './auth.js';
export { validateRequest } from './validation.js';
export { logRequest } from './logging.js';Create backend/services/ directory:
backend/
services/
courseService.js
examService.js
resourceService.js
Move business logic from controllers to services:
// services/examService.js
export async function getExamsByCourse(courseId) {
// Business logic here
}
// controllers/exams.js
import { getExamsByCourse } from '../services/examService.js';
export async function getExams(req, res) {
const exams = await getExamsByCourse(req.query.courseId);
res.json({ ok: true, data: exams });
}cd backend
npm install redis
npm install -D @types/redisCreate backend/services/cacheService.js:
import { createClient } from 'redis';
const client = createClient({
url: process.env.REDIS_URL || 'redis://localhost:6379'
});
client.on('error', (err) => console.error('Redis Client Error', err));
await client.connect();
export async function getCached(key) {
const value = await client.get(key);
return value ? JSON.parse(value) : null;
}
export async function setCached(key, value, ttl = 3600) {
await client.setEx(key, JSON.stringify(value), ttl);
}
export { client };import { getCached, setCached } from '../services/cacheService.js';
const cacheKey = `courses:${courseId}`;
let courses = await getCached(cacheKey);
if (!courses) {
courses = await fetchCourses(courseId);
await setCached(cacheKey, courses, 3600);
}Add indexes in Supabase SQL editor:
-- Index for course lookups
CREATE INDEX idx_resources_course_id ON resources(course_id);
-- Index for exam lookups
CREATE INDEX idx_exams_course_id ON exams(course_id);
-- Index for user lookups
CREATE INDEX idx_users_username ON users(username);Update components:
<button aria-label="Close modal" aria-describedby="modal-description" on:click={close}>
Close
</button>Add keyboard handlers:
<script>
function handleKeydown(event) {
if (event.key === 'Escape') {
close();
}
}
</script>
<div onkeydown={handleKeydown} tabindex="0">
<!-- content -->
</div>Add semantic HTML:
<nav aria-label="Main navigation">
<ul role="list">
<li role="listitem">
<a href="/courses">Courses</a>
</li>
</ul>
</nav>npm install -D @axe-core/playwright eslint-plugin-jsx-a11y# Frontend
npm install @sentry/sveltekit
# Backend
cd backend
npm install @sentry/nodeCreate src/lib/sentry.ts:
import * as Sentry from '@sentry/sveltekit';
Sentry.init({
dsn: import.meta.env.VITE_SENTRY_DSN,
environment: import.meta.env.MODE,
tracesSampleRate: 1.0
});Update src/hooks.client.ts:
import '../lib/sentry';For SvelteKit serverless functions, Sentry integrates differently. Add to src/hooks.server.ts:
import * as Sentry from '@sentry/sveltekit';
Sentry.init({
dsn: import.meta.env.SENTRY_DSN,
environment: import.meta.env.NODE_ENV,
tracesSampleRate: 1.0
});
// Error handling is already in src/lib/server/errors.ts
// Sentry will capture errors automatically via hooksnpm install posthog-js// src/lib/analytics.ts
import posthog from 'posthog-js';
if (typeof window !== 'undefined') {
posthog.init(import.meta.env.VITE_POSTHOG_KEY, {
api_host: 'https://app.posthog.com'
});
}
export { posthog };cd backend
npm install winstonCreate backend/utils/winston.js:
import winston from 'winston';
export const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' })
]
});
if (process.env.NODE_ENV !== 'production') {
logger.add(
new winston.transports.Console({
format: winston.format.simple()
})
);
}- Install testing dependencies and create test files
- Configure bundle optimization
- Set up Swagger/OpenAPI documentation
- Implement Redis caching
- Add database indexes
- Improve accessibility (ARIA labels, keyboard nav)
- Set up Sentry for error tracking
- Configure analytics
- Set up Winston logging
Add these to your .env files:
# Error Tracking
SENTRY_DSN=your_sentry_dsn
VITE_SENTRY_DSN=your_frontend_sentry_dsn
# Analytics
VITE_POSTHOG_KEY=your_posthog_key
# Caching
REDIS_URL=redis://localhost:6379
# Logging
LOG_LEVEL=INFOFor detailed implementation of any section, refer to the specific tool documentation or ask for help with specific configurations.