Français | English
A full-stack AI language learning platform that provides an immersive, interactive practice environment. Engage in dynamic conversations, master grammar, expand vocabulary, and test your skills with real-world scenarios. All powered by a modern Node.js backend with full user authentication and persistent cloud storage.
Demo video and screenshots in the
/screenshotsdirectory.
AI.LL.Showcase.mp4
From left to right: Flashcard Interface, Dark Mode Snapshot, and Grammar Mode.
AI Language Learning is a full-stack web application that provides an interactive environment for practicing foreign languages with an AI tutor.
Users can engage in structured learning modes such as conversation, grammar correction, vocabulary building, and scenario-based roleplay. The system maintains persistent chat sessions, learning history, and flashcards to support long-term progression.
The application is built as a RESTful client-server architecture using a Node.js + Express backend and a vanilla JavaScript frontend. It integrates an AI model via OpenRouter and stores user data in a PostgreSQL database.
The goal of this project is to simulate a production-style learning platform with authentication, persistent state, and modular backend services, rather than a simple chat interface.
Switch instantly between focused practice styles:
- Conversation — Practice natural, open-ended dialogue. The AI gently corrects mistakes and asks follow-up questions.
- Grammar — Receive targeted corrections, detailed rule explanations, and practice exercises.
- Vocabulary — Learn new words, synonyms, and contextual usage examples.
- Roleplay — Immerse yourself in real-world scenarios such as ordering at a restaurant, checking into a hotel, or navigating an airport.
The AI speaks a sentence in the target language, you repeat it using your microphone, and you get instant feedback with an accuracy score to sharpen both comprehension and pronunciation.
Automatically generate a vocabulary deck from your conversation history. Study in a dedicated flashcard interface, mark cards as known or unknown, and track progress over time.
- Speech-to-Text — Speak your responses instead of typing.
- Text-to-Speech — Enable Auto-Read to have AI responses spoken aloud. Replay or stop playback at any time.
- Secure sign-up and login with bcrypt-hashed passwords.
- JWT-based access tokens (15-minute expiry) with rotating refresh tokens (30-day expiry) stored as HttpOnly cookies.
- Token rotation on every refresh and explicit logout revocation.
When logged in, all chat sessions, messages, flashcards, and settings sync to a PostgreSQL database, so your data follows you across devices.
- Create, rename, delete, and switch between multiple chat sessions.
- Per-session language, difficulty, mode, and auto-read settings are saved automatically.
- Guests use browser
localStorage; authenticated users sync to the cloud.
- Languages: Spanish, French, German, Italian, Japanese, Korean, Mandarin Chinese, Portuguese, Modern Standard Arabic, and Hindi.
- Difficulty levels: Beginner, Intermediate, Advanced.
- Light and dark theme toggle.
The system is designed as a layered full-stack architecture separating UI, API logic, services, and persistence for scalability and maintainability.
Frontend (Vanilla JS)
↓
REST API (Express)
↓
Controllers Layer
↓
Services (AI / Auth / Logic)
↓
PostgreSQL Database
The application uses a centralized error handling pattern:
AppError(src/utils/AppError.js) — a custom error class that wraps a message and an HTTP status code, used throughout controllers to throw consistent, typed errors.errorHandler(src/middleware/errorHandler.js) — an Express error-handling middleware registered at the end of the middleware chain. It catches all thrownAppErrorinstances (and unexpected errors) and returns a uniform JSON error response, keeping error logic out of individual controllers.
| Layer | Technology |
|---|---|
| Frontend | HTML5, CSS3, Vanilla JavaScript |
| Backend | Node.js, Express 5 |
| Architecture Style | RESTful API |
| AI Service | OpenRouter API (OpenAI-compatible) |
| Database | PostgreSQL (pg) |
| Storage | Cloud + local hybrid state system |
| Auth | JWT (jsonwebtoken) (refresh rotation) + bcrypt |
| Dev tooling | Nodemon, dotenv |
| Testing | Jest |
All API routes are prefixed with /api.
| Method | Endpoint | Description |
|---|---|---|
POST |
/signup |
Register a new user |
POST |
/login |
Log in and receive tokens |
POST |
/logout |
Revoke refresh token and clear cookies |
POST |
/refresh |
Rotate refresh token, issue new access token |
GET |
/me |
Return current authenticated user |
| Method | Endpoint | Description |
|---|---|---|
GET |
/ |
List all chats for the user |
GET |
/:id |
Get a single chat with its messages |
POST |
/ |
Create a new chat session |
PUT |
/:id |
Update chat settings (title, mode, language, etc.) |
DELETE |
/:id |
Delete a chat and all its messages |
POST |
/:id/messages |
Append a message to a chat |
| Method | Endpoint | Description |
|---|---|---|
GET |
/ |
List all flashcards |
POST |
/ |
Add a new flashcard |
PUT |
/:id |
Update a flashcard (e.g., mark known) |
DELETE |
/:id |
Delete a flashcard |
| Method | Endpoint | Description |
|---|---|---|
POST |
/ask |
Send a prompt; returns an AI tutor response |
| Method | Endpoint | Description |
|---|---|---|
GET |
/:key |
Retrieve a stored value by key |
PUT |
/:key |
Set / upsert a stored value |
DELETE |
/:key |
Delete a stored key |
| Table | Purpose |
|---|---|
users |
Email, hashed password, display name |
refresh_tokens |
Persisted refresh tokens with expiry |
user_settings |
Per-user theme, language, difficulty, auto-read preferences |
user_storage |
Generic key/value store for syncing client state |
chats |
Chat sessions with mode, language, difficulty, scenario settings |
chat_messages |
Individual messages (sender, text, HTML) per chat |
flashcards |
Vocabulary cards with known/unknown status and review count |
ai-language-learning/
├── public/ # Static frontend
│ ├── index.html
│ ├── logo.svg / logo.ico
│ ├── css/
│ │ ├── main.css # Main entry point (imports all below)
│ │ ├── base.css # Reset & base styles
│ │ ├── dark.css # Dark mode theme
│ │ ├── auth.css # Authentication modal styles
│ │ ├── chat.css # Chat window & message styles
│ │ ├── input.css # Input area & button styles
│ │ └── header.css # Header, nav, mode selector styles
│ └── js/
│ ├── main.js # App entry point & UI logic
│ ├── auth.js # Authentication flow
│ ├── chat.js # Chat session management
│ ├── flashcards.js # Flashcard interface
│ ├── languages.js # Shared language definitions
│ ├── listening.js # Listening practice module
│ ├── appStorage.js # Cloud storage sync
│ └── storage.js # Local storage helpers
├── src/
│ ├── controllers/
│ │ ├── aiController.js # AI prompt construction & OpenRouter calls
│ │ ├── authController.js # Signup, login, logout, refresh, /me
│ │ ├── chatController.js # Chat CRUD + message management
│ │ ├── flashcardController.js
│ │ ├── storageController.js # Key/value cloud storage per user
│ │ └── userController.js
│ ├── db/
│ │ ├── connection.js # pg Pool setup
│ │ └── schema.sql # Full DB schema
│ ├── middleware/
│ │ ├── authMiddleware.js # JWT verification middleware
│ │ └── errorHandler.js # Centralized AppError handler
│ ├── routes/
│ │ ├── ai.js
│ │ ├── auth.js
│ │ ├── chats.js
│ │ ├── flashcards.js
│ │ ├── storage.js
│ │ └── users.js
│ ├── services/
│ │ └── openrouter.js # OpenAI-compatible client for OpenRouter
│ └── utils/
│ ├── AppError.js # Custom error class with status codes
│ ├── hash.js # bcrypt helpers
│ └── jwt.js # Access & refresh token signing/verification
├── tests/
│ ├── authController.test.js
│ ├── aiController.test.js
│ ├── chatController.test.js
│ ├── flashcardController.test.js
│ ├── authMiddleware.test.js
│ ├── hash.test.js
│ └── jwt.test.js
├── screenshots/
├── jest.config.js # Jest configuration
├── server.js # Express app entry point
├── package.json
└── .env # Environment variables (not committed)
- Access tokens — short-lived JWTs (15 min) delivered as an
HttpOnlycookie. - Refresh tokens — long-lived JWTs (30 days) stored in both an
HttpOnlycookie (path-restricted to/api/auth/refresh) and the database for revocation. - Token rotation — every refresh call deletes the old token and issues a new pair, preventing replay attacks.
- Logout — explicitly deletes the refresh token from the database and clears both cookies.
- Synchronizing AI chat state with persistent database storage
- Designing secure token rotation with refresh token revocation
- Managing dual storage system (localStorage vs PostgreSQL)
- Structuring modular backend controllers for scalability
- Node.js v18+ and npm
- A PostgreSQL database (local or hosted)
- An OpenRouter API key — get one here
-
Clone the repository
git clone https://github.com/deryokoya/ai-language-learning.git cd ai-language-learning -
Install dependencies
npm install
-
Set up the database
Run the schema file against your PostgreSQL instance:
psql -U <your_user> -d <your_database> -f src/db/schema.sql
-
Configure environment variables
Create a
.envfile in the project root:OPENROUTER_API_KEY="your_openrouter_api_key_here" # PostgreSQL connection string DATABASE_URL="postgresql://user:password@localhost:5432/your_database" # JWT secret (use long, random strings) JWT_SECRET="your_jwt_secret_here"
-
Run the application
Development mode (auto-reload with Nodemon):
npm run dev
Production mode:
npm start
-
Open in your browser
http://localhost:3000
The project includes a full Jest test suite covering controllers, middleware, and utilities. See the Testing section for details and the full file breakdown.
The project ships with a Jest test suite covering the core backend:
| File | What it tests |
|---|---|
authController.test.js |
Signup, login, logout, refresh, and /me flows |
aiController.test.js |
AI prompt construction and OpenRouter response handling |
chatController.test.js |
Chat CRUD operations and message management |
flashcardController.test.js |
Flashcard creation, updates, and deletion |
authMiddleware.test.js |
JWT verification and request guard behaviour |
hash.test.js |
bcrypt hashing and comparison helpers |
jwt.test.js |
Access and refresh token signing and verification |
Run the suite:
npm test # run once
npm run test:coverage # with coverage reportPull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
This project is open source. See the repository for license details.


