NewEd is a full-stack educational platform that combines interactive learning
environments, community forums, real-time AI assistance, and 3D graphics to create an immersive learning
experience. Built with FastAPI on the backend and sophisticated JavaScript on the frontend,
this project demonstrates expertise in modern web architecture, asynchronous programming, database design,
and advanced UI/UX implementation.
🏗 Architecture & Technology Stack
Backend
- Framework: FastAPI (Python 3.13)
- Database: PostgreSQL with SQLAlchemy ORM
- Authentication: JWT-based session management with secure cookie handling
- Middleware: Custom rate limiting, CORS, GZip compression
- Template Engine: Jinja2 with custom filters
- External APIs: Frontiers Research API, AI text generation
Frontend
- Rendering: Server-side rendered templates with progressive enhancement
- 3D Graphics: Three.js with WebGL for animated 3D characters
- Rich Text Editing: Quill.js integration
- Interactivity: Custom task systems (sortable, notation, code, chat tasks)
- Responsive Design: Mobile-first approach with comprehensive media queries
🎯 Frontend Deep Dive — Interactive Task System
The learning environment implements a sophisticated gamified learning system with multiple interactive
task types. This modular task architecture showcases DOM manipulation mastery,
state management, and event-driven programming.
Task Completion & Content Gating
function blurContentAfterTask(currentTaskId) {
const articleContent = document.querySelector('#article_content');
const children = Array.from(articleContent.children);
let blur = false;
children.forEach(child => {
if (child.id === currentTaskId) {
blur = true;
} else if (blur) {
child.classList.add('blurred');
child.classList.remove('unblurred');
}
});
}
function unblurContentUpToNextTask(currentTaskId) {
const articleContent = document.querySelector('#article_content');
const children = Array.from(articleContent.children);
let unblur = false;
children.forEach(child => {
if (child.id === currentTaskId) {
unblur = true;
} else if (unblur) {
if (child.classList.contains('task_fake')) {
unblur = false;
}
child.classList.remove('blurred');
child.classList.add('unblurred');
}
});
}
Key Technical Insights:
- Content is progressively revealed as users complete tasks, creating a "gated" learning flow
- CSS blur filters are dynamically toggled on DOM elements based on task completion state
- The pattern uses a finite state machine approach where each task completion triggers
the next state transition
Typewriter Effect with Audio Synthesis
The platform features an immersive typewriter animation with Web Audio API integration
for sound feedback:
function typeWriter(element, htmlContent, delay = 50, playAnimation = false) {
element.html('');
const typingCaret = $('<span class="typing-caret"></span>');
element.append(typingCaret);
let i = 0;
let tag = false;
let currentTag = '';
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
function playTypingSound() {
if (!audioContext) return;
const oscillator = audioContext.createOscillator();
oscillator.type = 'sine';
oscillator.frequency.setValueAtTime(600, audioContext.currentTime);
const gainNode = audioContext.createGain();
oscillator.connect(gainNode);
gainNode.connect(audioContext.destination);
// Smooth fade in and fade out
gainNode.gain.setValueAtTime(0, audioContext.currentTime);
gainNode.gain.linearRampToValueAtTime(0.1, audioContext.currentTime + 0.01);
gainNode.gain.linearRampToValueAtTime(0, audioContext.currentTime + 0.04);
oscillator.start();
oscillator.stop(audioContext.currentTime + 0.05);
}
function startTyping() {
const typingInterval = setInterval(() => {
if (i < htmlContent.length) {
let char = htmlContent[i];
if (char === '<') {
tag = true;
currentTag += char;
} else if (char === '>') {
tag = false;
currentTag += char;
typingCaret.before(currentTag);
currentTag = '';
} else if (tag) {
currentTag += char;
} else {
typingCaret.before(document.createTextNode(char));
playTypingSound();
}
i++;
} else {
clearInterval(typingInterval);
typingCaret.remove();
}
}, delay);
}
startTyping();
}
Technical Highlights:
- HTML-aware parsing: The algorithm correctly handles HTML tags, buffering them until
complete before inserting
- Web Audio API: Real-time audio synthesis creates typing sounds without loading audio
files
- Gain envelope: Volume ramping prevents audio clicks for smooth, natural sound
Bionic Reading Toggle — Text Processing
A unique accessibility feature that implements "bionic reading" by bolding the first half of each word:
function fastify(str) {
let array = str.split(' ');
let sentence = [];
for (let x = 0; x < array.length; x++) {
let word = array[x];
if (!word) {
sentence.push('');
continue;
}
let pre, last;
if (word.length >= 1 && word.length <= 3) {
pre = `<b>${word.substring(0, 1)}</b>`;
last = word.substring(1);
} else if (word.length >= 4) {
if (word.length % 2 === 0) {
pre = `<b>${word.substring(0, word.length / 2)}</b>`;
last = word.substring(word.length / 2);
} else {
pre = `<b>${word.substring(0, Math.ceil(word.length / 2))}</b>`;
last = word.substring(Math.ceil(word.length / 2));
}
}
sentence.push(pre + last);
}
return sentence.join(' ');
}
🌐 Community Page — AJAX Papers Retrieval
The community forum implements asynchronous API integration with the Frontiers Research
database, dynamically loading academic papers relevant to each subject.
Frontend Implementation
document.addEventListener('DOMContentLoaded', function () {
const papersContainer = document.getElementById('papers-container');
const loadingSpinner = document.getElementById('loading-spinner');
// Fetch papers from backend API
fetch(`/get_papers/{{ subject.codename }}`)
.then(response => response.json())
.then(papers => {
loadingSpinner.style.display = 'none';
papers.forEach(paper => {
const paperElement = document.createElement('a');
paperElement.href = paper.url;
paperElement.innerHTML = `
<div class="paper">
<div class="paper_content">
<div class="paper_name">${paper.title}</div>
<div class="paper_right">
<div class="paper_stats">
<span class="paper_citations">${paper.citations}</span>
<span class="paper_views">${paper.views}</span>
</div>
<div class="paper_date">${paper.date}</div>
</div>
</div>
</div>`;
papersContainer.appendChild(paperElement);
});
})
.catch(error => {
console.error('Error loading papers:', error);
loadingSpinner.innerHTML = '<p>Error loading papers.</p>';
});
});
Backend Service — Frontiers API Integration
import httpx
import logging
logger = logging.getLogger(__name__)
async def get_latest_articles_correct(subject: str):
"""Fetch latest articles from Frontiers API"""
url = "https://www.frontiersin.org/api/v3/journals/search/articles"
frontiers_ids = {'psychology': 36, 'nutrition': 628, 'brain': 1}
if subject not in frontiers_ids:
return []
headers = {
"accept": "application/json, text/plain, */*",
"content-type": "application/json",
"origin": "https://www.frontiersin.org",
}
data = {
"Skip": 16,
"Top": 5000,
"StartDate": "2024/05/12",
"EndDate": "2024/08/12",
"Filter": {
"JournalId": frontiers_ids[subject],
"Sort": 1,
},
}
try:
async with httpx.AsyncClient() as client:
response = await client.post(url, headers=headers, json=data, timeout=10)
response.raise_for_status()
response_json = response.json()
articles = response_json.get('articles', [])
# Sort by views and select top 30
sorted_articles = sorted(
articles,
key=lambda x: x.get('impact', {}).get('views', {}).get('count', 0),
reverse=True
)[:30]
return [
{
'title': article.get('title'),
'views': article.get('impact', {}).get('views', {}).get('count', 0),
'citations': article.get('impact', {}).get('citations', {}).get('count', 0),
'date': article.get('publishedDate'),
'url': article.get('publicUrl')
}
for article in sorted_articles
]
except Exception as e:
logger.error(f"Error fetching articles: {e}", exc_info=True)
return []
Technical Excellence:
- Async HTTP client (
httpx.AsyncClient) for non-blocking I/O
- Defensive programming with nested
.get() chains preventing KeyError
crashes
- Data transformation layer converting external API schema to internal format
- Proper error handling with structured logging
⚙️ Backend Architecture
Optimized Database Queries — N+1 Prevention
@router.get("/community/{com_sub}", response_class=HTMLResponse)
async def com_subject(com_sub: str, request: Request, db: Session = Depends(get_db)):
posts_feed = db.query(Post).filter_by(subject=com_sub).order_by(Post.date.desc()).all()
# ✅ OPTIMIZED: Single grouped query instead of N+1 queries
from sqlalchemy import func
reply_counts = db.query(
Reply.post_id,
func.count(Reply.id).label('count')
).filter(
Reply.post_id.in_([post.id for post in posts_feed])
).group_by(Reply.post_id).all()
# Convert to dictionary for O(1) lookup
post_replies_num = {post_id: count for post_id, count in reply_counts}
# Fill zeros for posts without replies
for post in posts_feed:
if post.id not in post_replies_num:
post_replies_num[post.id] = 0
Rate Limiting Middleware
class RateLimitMiddleware(BaseHTTPMiddleware):
"""Rate limiting middleware using in-memory storage."""
def __init__(self, app, requests_per_minute: int = 60):
super().__init__(app)
self.requests_per_minute = requests_per_minute
self.requests: Dict[str, list[Tuple[datetime, str]]] = defaultdict(list)
self.last_cleanup = datetime.now()
async def dispatch(self, request: Request, call_next):
client_ip = request.client.host
if "x-forwarded-for" in request.headers:
client_ip = request.headers["x-forwarded-for"].split(",")[0].strip()
# Skip for health checks
if request.url.path in ["/health", "/healthz", "/"]:
return await call_next(request)
now = datetime.now()
# Cleanup old entries periodically
if (now - self.last_cleanup).seconds > 60:
self._cleanup_old_entries()
self.last_cleanup = now
# Rate limit check
request_list = self.requests[client_ip]
cutoff = now - timedelta(minutes=1)
request_list[:] = [(ts, p) for ts, p in request_list if ts > cutoff]
if len(request_list) >= self.requests_per_minute:
raise HTTPException(
status_code=status.HTTP_429_TOO_MANY_REQUESTS,
headers={"Retry-After": "60"}
)
request_list.append((now, request.url.path))
response = await call_next(request)
# Add rate limit headers
response.headers["X-RateLimit-Limit"] = str(self.requests_per_minute)
response.headers["X-RateLimit-Remaining"] = str(max(0, self.requests_per_minute - len(request_list)))
return response
SQLAlchemy Models — RBAC Implementation
class UserRole(str, enum.Enum):
"""User role enumeration for RBAC"""
USER = "user"
ADMIN = "admin"
MODERATOR = "moderator"
class User(Base):
__tablename__ = 'Users'
id = Column(Integer, primary_key=True)
role = Column(SQLEnum(UserRole), default=UserRole.USER)
def is_admin(self):
return self.role == UserRole.ADMIN
def has_permission(self, required_role: UserRole):
role_hierarchy = {
UserRole.USER: 0,
UserRole.MODERATOR: 1,
UserRole.ADMIN: 2
}
return role_hierarchy.get(self.role, 0) >= role_hierarchy.get(required_role, 0)
🧪 3D Graphics — Interactive Chemistry Simulation
The platform features an advanced chemistry learning task with interactive 3D atomic
simulations. Users can drag atoms to form molecules, triggering visual feedback with sparkle effects and
sidebar information panels.
Scene Initialization with Post-Processing Effects
import * as THREE from 'three';
import { EffectComposer } from 'EffectComposer';
import { RenderPass } from 'RenderPass';
import { UnrealBloomPass } from 'UnrealBloomPass';
let scene, camera, renderer, composer;
let atomMeshes = [];
let raycaster = new THREE.Raycaster();
let mouse = new THREE.Vector2();
const molecules = [
{
elements: ['Na', 'Cl'],
name: 'Sodium Chloride',
formula: 'NaCl',
description: 'Table salt, widely used in food seasoning and preservation.'
},
{
elements: ['H', 'F'],
name: 'Hydrogen Fluoride',
formula: 'HF',
description: 'Essential in fluorine compound production and glass etching.'
}
];
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 25;
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.getElementById('scene-container').appendChild(renderer.domElement);
// Effect Composer for Bloom Effect
composer = new EffectComposer(renderer);
composer.addPass(new RenderPass(scene, camera));
const bloomPass = new UnrealBloomPass(
new THREE.Vector2(window.innerWidth, window.innerHeight),
0.8, // Bloom strength
1, // Bloom radius
0 // Bloom threshold
);
composer.addPass(bloomPass);
createFloatingAtoms();
animate();
}
Dynamic Atom Creation with Canvas Textures
function createFloatingAtoms() {
const sphereGeometry = new THREE.SphereGeometry(2, 64, 64);
nobleGases.forEach((element, index) => {
// Create Canvas for Base Texture with element symbol
const canvas = document.createElement('canvas');
const size = 512;
canvas.width = size;
canvas.height = size;
const context = canvas.getContext('2d');
// Draw transparent purple glass base
context.fillStyle = 'rgba(128, 0, 128, 0.6)';
context.fillRect(0, 0, size, size);
// Draw chemical symbol in center
context.font = 'bold 100px Arial';
context.fillStyle = 'black';
context.textAlign = 'center';
context.textBaseline = 'middle';
context.fillText(element.symbol, size / 2, size / 2);
const baseTexture = new THREE.CanvasTexture(canvas);
// Create Emissive Map for glow effect
const emissiveCanvas = document.createElement('canvas');
emissiveCanvas.width = size;
emissiveCanvas.height = size;
const emissiveContext = emissiveCanvas.getContext('2d');
emissiveContext.fillStyle = 'white';
emissiveContext.fillRect(0, 0, size, size);
emissiveContext.font = 'bold 150px Arial';
emissiveContext.fillStyle = 'black';
emissiveContext.textAlign = 'center';
emissiveContext.textBaseline = 'middle';
emissiveContext.fillText(element.symbol, size / 2, size / 2);
const emissiveTexture = new THREE.CanvasTexture(emissiveCanvas);
const material = new THREE.MeshStandardMaterial({
map: baseTexture,
emissive: getRandomColor(),
emissiveIntensity: 2,
emissiveMap: emissiveTexture,
metalness: 1.1,
roughness: 0,
opacity: 0.5,
side: THREE.DoubleSide,
});
const atom = new THREE.Mesh(sphereGeometry, material);
atom.position.set(
Math.random() * 30 - 15,
Math.random() * 20 - 12,
Math.random() * 5 - 5
);
atom.originalPosition = atom.position.clone();
atom.elementData = element;
atom.noiseOffset = Math.random() * 1000;
scene.add(atom);
atomMeshes.push(atom);
});
}
Drag-and-Drop Atom Interaction
let isDragging = false;
let selectedAtom = null;
let dragPlane = new THREE.Plane();
let offset = new THREE.Vector3();
function onMouseDown(event) {
event.preventDefault();
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -((event.clientY / window.innerHeight) * 2 - 1);
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(atomMeshes, true);
if (intersects.length > 0) {
isDragging = true;
selectedAtom = intersects[0].object;
selectedAtom.isAnimating = false; // Pause floating animation
// Define drag plane perpendicular to camera
dragPlane.setFromNormalAndCoplanarPoint(
camera.getWorldDirection(new THREE.Vector3()),
selectedAtom.position
);
// Calculate offset for smooth dragging
if (raycaster.ray.intersectPlane(dragPlane, dragIntersection)) {
offset.copy(dragIntersection).sub(selectedAtom.position);
}
}
}
function onMouseUp(event) {
if (isDragging && selectedAtom) {
isDragging = false;
selectedAtom.originalPosition.copy(selectedAtom.position);
selectedAtom.isAnimating = true;
checkMoleculeFormation(selectedAtom);
}
selectedAtom = null;
}
Molecule Formation Detection
function checkMoleculeFormation(atom) {
const proximityThreshold = 5;
atomMeshes.forEach((otherAtom) => {
if (otherAtom !== atom && otherAtom.parent === scene) {
const distance = atom.position.distanceTo(otherAtom.position);
if (distance < proximityThreshold) {
const molecule = molecules.find(
(m) =>
m.elements.includes(atom.elementData.symbol) &&
m.elements.includes(otherAtom.elementData.symbol)
);
if (molecule) {
formMolecule(atom, otherAtom, molecule);
}
}
}
});
}
function formMolecule(atom1, atom2, molecule) {
const moleculeGroup = new THREE.Group();
moleculeGroup.userData.isMolecule = true;
moleculeGroup.userData.moleculeData = molecule;
scene.remove(atom1);
scene.remove(atom2);
moleculeGroup.add(atom1);
moleculeGroup.add(atom2);
createBondMesh(atom1.position, atom2.position, moleculeGroup);
createSparkles(moleculeGroup);
scene.add(moleculeGroup);
populateSidebar(molecule);
showSidebar();
}
Animation Loop with Floating Effect
function animate() {
requestAnimationFrame(animate);
const time = Date.now() * 0.0005;
atomMeshes.forEach((atom) => {
if (atom.isAnimating !== false) {
const noiseFactor = 0.5;
// Organic floating motion
atom.position.x = atom.originalPosition.x + Math.sin(time + atom.noiseOffset) * noiseFactor;
atom.position.y = atom.originalPosition.y + Math.sin(time + atom.noiseOffset + 10) * noiseFactor;
atom.position.z = atom.originalPosition.z + Math.sin(time + atom.noiseOffset + 20) * noiseFactor;
// Oscillatory rotation
const rotationAmplitude = THREE.MathUtils.degToRad(45);
atom.rotation.y = atom.initialRotationY + Math.sin(time + atom.noiseOffset) * rotationAmplitude;
}
});
composer.render();
}
Technical Highlights:
- Post-processing pipeline: UnrealBloomPass creates glowing atomic effects
- Canvas-based textures: Dynamic generation of element symbols with emissive maps
- Raycaster drag system: Plane intersection for accurate 3D dragging
- Molecule detection algorithm: Proximity-based bonding with chemical validation
- Particle effects: Sparkle system for visual feedback on molecule formation
🔐 Security Implementation
JWT Session Management
app.add_middleware(
SessionMiddleware,
secret_key=settings.SECRET_KEY,
max_age=86400, # 24 hours
same_site="lax",
https_only=not settings.DEBUG, # HTTPS in production
)
Endpoint-Specific Rate Limiters
login_limiter = EndpointRateLimiter(max_requests=5, window_seconds=300)
verification_limiter = EndpointRateLimiter(max_requests=3, window_seconds=180)
signup_limiter = EndpointRateLimiter(max_requests=3, window_seconds=3600)
ai_limiter = EndpointRateLimiter(max_requests=10, window_seconds=60)
📊 Conclusion
NewEd demonstrates proficiency across the full stack:
- Frontend: Advanced JavaScript patterns, 3D graphics, accessibility features, and
responsive design
- Backend: Async Python, database optimization, security middleware, and external API
integration
- Architecture: Clean separation of concerns, modular route organization, and service
layer patterns
- UX: Gamification, progressive content disclosure, real-time feedback, and immersive
learning
Built with FastAPI, Three.js, SQLAlchemy, and modern JavaScript