To develop a Full Stack Language Learning App with all the desired features and functionality, you can follow this high-level breakdown of the architecture, along with detailed steps for both front-end and back-end implementation. This solution integrates Next.js for the web front-end, NestJS for the back-end API, and React Native for mobile app development. Here's a comprehensive guide:
1. Architecture Overview
Tech Stack:
- Frontend (Web): Next.js with Tailwind CSS for responsive design.
- Backend: NestJS with TypeORM (or Prisma), PostgreSQL for database management, and JWT for authentication.
- Mobile App: React Native for cross-platform development (iOS and Android).
- APIs: REST or GraphQL for mobile communication with the backend.
- Hosting: Vercel or Netlify for Next.js, and cloud providers like AWS or Heroku for NestJS API.
2. Features Breakdown
Frontend (Web):
-
Language Courses Overview:
- Display a list of available language courses.
- Provide course details with progress tracking for users.
-
Lessons & Quizzes:
- Each course contains lessons with multimedia content (text, audio, video).
- Quizzes for assessing users' understanding after each lesson.
- Display quiz results and feedback.
-
Progress Tracking:
- Show completed lessons, total progress, and achievements.
- Implement a dashboard for users to track their progress.
Backend (API):
-
User Authentication:
- Implement authentication using JWT with refresh tokens.
- Allow users to register, login, reset passwords, and manage profiles.
-
Course & Lesson Management (Admin):
- CRUD operations for language courses, lessons, quizzes, and media content.
- Implement an admin panel for managing content (Next.js with an admin layout).
-
Quiz Result Tracking:
- Save and analyze quiz results for each user.
- Provide analytics on users' performance in quizzes.
-
User Profiles:
- Manage user information, learning history, and preferences.
- Store progress and completion rates of courses and lessons.
Mobile App (React Native):
-
Interactive Lessons:
- Support offline mode for lessons and quizzes.
- Gamified learning experience with quizzes, drag-and-drop activities, and real-time feedback.
-
Vocabulary Games:
- Implement vocabulary matching games or flashcards.
- Include streaks, badges, and leaderboards for user engagement.
3. System Components Breakdown
Backend Setup:
- Use NestJS for scalable and maintainable APIs. Here's a basic folder structure:
src/
├── auth/ # JWT Authentication (Login/Register)
├── users/ # User Profile & Progress Management
├── courses/ # Course Management (Lessons, Quizzes)
├── quizzes/ # Quizzes and Results Tracking
├── database/ # PostgreSQL or MongoDB integration
├── notifications/ # Email and Push Notifications
├── app.module.ts # Root Module
- Authentication: JWT-based, stored in HTTP-only cookies for web, and in secure storage for mobile.
- Database: PostgreSQL with TypeORM or Prisma for relational data.
- File Uploads: Use AWS S3 for storing lesson media (audio, video) or Firebase for smaller-scale apps.
Frontend (Next.js) Setup:
- Routing and Pages: Use file-based routing in Next.js for defining pages like courses, lessons, quizzes, and user profiles.
- UI/UX: Use Tailwind CSS for styling and responsiveness.
- State Management: Leverage React Context or Redux for managing global states like user session, progress, etc.
Example Folder Structure for Next.js:
pages/
├── courses/ # List of courses
│ └── [courseId].tsx # Individual course with lessons
├── profile.tsx # User profile and progress tracking
├── quizzes/[quizId].tsx # Quiz page
├── api/ # API Routes if needed
components/
├── LessonCard.tsx # Reusable component for lessons
├── QuizComponent.tsx # Interactive quizzes
Mobile App (React Native) Setup:
- Use React Navigation for navigating between screens like lessons, quizzes, and the profile page.
- Use Expo for faster development and easier deployment.
Example Folder Structure for React Native:
src/
├── screens/
│ ├── CourseScreen.js # Lists lessons for a course
│ ├── QuizScreen.js # Interactive quizzes
│ └── ProfileScreen.js # User profile with progress
├── components/
│ ├── LessonCard.js # Component for displaying lessons
│ └── QuizCard.js # Component for interactive quiz
├── navigation/
│ └── AppNavigator.js # React Navigation setup
4. Implementation Steps
Step 1: Backend (NestJS) Development
-
Setup NestJS: Use
nestjs/cli
to scaffold your project:
nest new language-app-backend
- Install Dependencies:
npm install @nestjs/typeorm typeorm pg bcryptjs
npm install @nestjs/jwt passport-jwt
-
API Routes:
-
/auth/register
: User registration. -
/auth/login
: User login. -
/courses
: Get all courses. -
/courses/:id
: Get lessons in a course. -
/quizzes
: Handle quiz submissions and results.
-
Step 2: Frontend (Next.js) Development
- Setup Next.js:
npx create-next-app@latest my-language-app
- Install Tailwind CSS:
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init
- Setup Pages and Components for courses, lessons, quizzes, and progress tracking.
Step 3: Mobile App (React Native) Development
- Setup React Native using Expo:
npx create-expo-app my-language-app
- Install Dependencies:
npm install react-navigation react-native-async-storage
- Build Screens for lessons, interactive quizzes, and progress tracking.
5. Advanced Features:
- Push Notifications: Integrate Firebase for sending push notifications about progress updates or new lessons.
- Progress Sync: Ensure users' progress syncs across web and mobile platforms.
- Analytics: Track user engagement, lesson completion rates, and quiz performance using a tool like Google Analytics.
This architecture covers all key aspects and technologies for your Language Learning App across web and mobile platforms.
For a Next.js full-stack Language Learning App, I'll provide a detailed folder structure that aligns with the typical setup, incorporating both frontend features like pages for courses and quizzes, and backend integration via API routes. This folder structure assumes you are using the App Router (from Next.js 13+) for routing and Tailwind CSS for styling.
1. Folder Structure Overview
my-language-app/
├── app/
│ ├── (auth)/
│ │ ├── login/
│ │ │ └── page.tsx
│ │ ├── register/
│ │ │ └── page.tsx
│ ├── (dashboard)/
│ │ ├── layout.tsx
│ │ ├── page.tsx
│ ├── courses/
│ │ ├── page.tsx
│ │ ├── [courseId]/
│ │ │ ├── page.tsx
│ │ │ ├── quiz/
│ │ │ │ └── page.tsx
│ ├── profile/
│ │ └── page.tsx
│ ├── layout.tsx
│ ├── page.tsx
├── components/
│ ├── CourseCard.tsx
│ ├── Header.tsx
│ ├── QuizComponent.tsx
├── lib/
│ ├── api.ts
│ ├── auth.ts
├── styles/
│ ├── globals.css
│ └── tailwind.config.js
├── public/
│ ├── logo.png
│ └── favicon.ico
├── utils/
│ ├── fetcher.ts
│ ├── constants.ts
├── pages/
│ ├── api/
│ │ ├── auth/
│ │ │ └── [...nextauth].ts
│ │ ├── courses.ts
│ │ ├── quizzes.ts
│ └── _document.tsx
├── prisma/ (if using Prisma)
│ ├── schema.prisma
├── .env.local
├── next.config.js
├── tsconfig.json
└── package.json
2. Detailed Breakdown
app/
(App Router Directory)
This is the core of the app where Next.js routes are defined. Each folder inside this directory maps to a route.
-
(auth)/
: A folder for authentication pages (e.g., login and registration). Wrapping it in parentheses ensures it doesn't show up in the URL.-
login/page.tsx
: Login page for the app. -
register/page.tsx
: Registration page.
-
-
(dashboard)/
: This folder handles the user dashboard and user profile.-
layout.tsx
: Layout for the dashboard, including a shared header, sidebar, or navigation elements. -
page.tsx
: Main dashboard page that includes user progress and summary.
-
-
courses/
: Contains the list of courses and individual course details.-
page.tsx
: List of all available language courses. -
[courseId]/page.tsx
: Dynamic route for an individual course.courseId
represents the ID of a specific course. -
[courseId]/quiz/page.tsx
: A quiz for the specific course.
-
profile/page.tsx
: User profile page that allows the user to view and manage their profile and track progress.layout.tsx
: Global layout for the app, used for rendering elements like headers, footers, or navigation bars across all pages.page.tsx
: Home page of the app.
components/
(Reusable UI Components)
This directory contains reusable React components that can be used throughout the application.
-
CourseCard.tsx
: A card component to display a course preview. -
Header.tsx
: A header component used across multiple pages. -
QuizComponent.tsx
: A component to handle quiz UI elements.
lib/
(Libraries and API utilities)
This directory holds utility functions and libraries for interacting with the backend or other APIs.
-
api.ts
: A central file for making API calls (e.g., fetching courses, submitting quizzes). -
auth.ts
: Utility for handling authentication (e.g., checking login status, managing tokens).
styles/
(CSS and Tailwind Configuration)
This folder contains global styles, as well as the Tailwind CSS configuration file.
-
globals.css
: Global CSS styles. -
tailwind.config.js
: Tailwind CSS configuration file where you can define custom themes, extend styles, and configure the design system.
public/
(Static Assets)
This directory is for public files like images, icons, and other static assets.
-
logo.png
: A logo for the app. -
favicon.ico
: Favicon used in the browser tab.
utils/
(Helper Functions and Constants)
This directory contains utility functions and constants that are used across the application.
-
fetcher.ts
: A helper function for data fetching (can be used withSWR
orReact Query
). -
constants.ts
: A file to store constant values like API URLs, roles, or status codes.
pages/api/
(API Routes)
This folder contains API routes that run server-side in Next.js. It is used for handling server-side functionality, such as handling authentication and CRUD operations.
-
auth/[...nextauth].ts
: NextAuth.js configuration for handling user authentication with providers like Google or GitHub. -
courses.ts
: API route for getting the list of courses. -
quizzes.ts
: API route for handling quiz submission and fetching results.
prisma/
(Database Schema for Prisma, if applicable)
If you're using Prisma for database management, this folder contains the schema file for defining your database models.
-
schema.prisma
: The Prisma schema file defining your models (e.g., User, Course, Lesson, Quiz, etc.).
Root-Level Files
-
.env.local
: Environment variables for the app (e.g., database connection string, API keys). -
next.config.js
: Configuration file for Next.js. You can configure things like image optimization, environment variables, and more. -
tsconfig.json
: TypeScript configuration file. -
package.json
: Lists dependencies, scripts, and other metadata for your project.
3. Key Concepts
Dynamic Routes:
app/courses/[courseId]/page.tsx
andapp/courses/[courseId]/quiz/page.tsx
leverage Next.js's dynamic routing to create pages that render based on the course or quiz ID.Reusability: Components like
CourseCard.tsx
andQuizComponent.tsx
are built to be reused throughout different parts of the app.Authentication Handling: The
pages/api/auth/[...nextauth].ts
file handles user authentication using NextAuth.js, providing support for social logins and secure session management.API Routes: The
pages/api/
folder contains the logic for managing backend operations, making this a full-stack Next.js application.
This folder structure is scalable and can accommodate new features, such as adding more quizzes, supporting more languages, or expanding user functionalities. Let me know if you'd like to expand on any part of this!
For a NestJS backend with PostgreSQL as the database, I'll provide a comprehensive folder structure that aligns with best practices. This structure supports modularity, scalability, and clean separation of concerns.
Here's the full NestJS backend folder structure:
1. Folder Structure Overview
my-language-app-backend/
├── src/
│ ├── auth/
│ │ ├── dto/
│ │ │ ├── login.dto.ts
│ │ │ └── register.dto.ts
│ │ ├── auth.controller.ts
│ │ ├── auth.module.ts
│ │ ├── auth.service.ts
│ │ ├── jwt.strategy.ts
│ │ └── local.strategy.ts
│ ├── users/
│ │ ├── dto/
│ │ │ ├── create-user.dto.ts
│ │ │ ├── update-user.dto.ts
│ │ ├── entities/
│ │ │ └── user.entity.ts
│ │ ├── users.controller.ts
│ │ ├── users.module.ts
│ │ ├── users.service.ts
│ ├── courses/
│ │ ├── dto/
│ │ │ ├── create-course.dto.ts
│ │ │ ├── update-course.dto.ts
│ │ ├── entities/
│ │ │ └── course.entity.ts
│ │ ├── courses.controller.ts
│ │ ├── courses.module.ts
│ │ ├── courses.service.ts
│ ├── lessons/
│ │ ├── dto/
│ │ │ ├── create-lesson.dto.ts
│ │ │ └── update-lesson.dto.ts
│ │ ├── entities/
│ │ │ └── lesson.entity.ts
│ │ ├── lessons.controller.ts
│ │ ├── lessons.module.ts
│ │ ├── lessons.service.ts
│ ├── quizzes/
│ │ ├── dto/
│ │ │ ├── create-quiz.dto.ts
│ │ │ └── submit-quiz.dto.ts
│ │ ├── entities/
│ │ │ └── quiz.entity.ts
│ │ ├── quizzes.controller.ts
│ │ ├── quizzes.module.ts
│ │ ├── quizzes.service.ts
│ ├── common/
│ │ ├── decorators/
│ │ │ └── roles.decorator.ts
│ │ ├── guards/
│ │ │ ├── jwt-auth.guard.ts
│ │ │ └── roles.guard.ts
│ │ └── utils/
│ │ └── hash.util.ts
│ ├── config/
│ │ ├── app.config.ts
│ │ └── database.config.ts
│ ├── database/
│ │ ├── migrations/
│ │ └── typeorm.config.ts
│ ├── app.module.ts
│ ├── main.ts
├── prisma/ (if using Prisma instead of TypeORM)
│ └── schema.prisma
├── .env
├── .gitignore
├── package.json
├── tsconfig.json
├── docker-compose.yml (optional for Docker setup)
└── README.md
2. Detailed Explanation
Core Folders
-
src/
: The root directory for your NestJS app. Contains all the source code for your application.
Auth Module (auth/
):
Handles authentication-related logic, including login, registration, and JWT strategies.
-
auth.controller.ts
: Handles HTTP requests related to authentication (login, register). -
auth.module.ts
: Defines the authentication module with all the dependencies like services, controllers, etc. -
auth.service.ts
: Handles authentication logic like verifying user credentials and issuing JWT tokens. -
jwt.strategy.ts
: Implements JWT-based authentication. -
local.strategy.ts
: Implements local (username/password) authentication strategy. -
dto/
: Data Transfer Objects for validating authentication-related data (e.g.,login.dto.ts
,register.dto.ts
).
Users Module (users/
):
Manages users, including creating, updating, and retrieving user data.
-
users.controller.ts
: Handles HTTP requests for user-related operations (e.g., profile management). -
users.module.ts
: Users module definition. -
users.service.ts
: Contains the business logic for interacting with the user repository (e.g., fetching user data, updating user profiles). -
entities/
: Contains TypeORM entities, specificallyuser.entity.ts
for defining theUser
model. -
dto/
: Contains DTOs for user operations (e.g.,create-user.dto.ts
,update-user.dto.ts
).
Courses Module (courses/
):
Manages language courses, including creating, updating, and retrieving courses.
-
courses.controller.ts
: Handles HTTP requests for courses (CRUD operations). -
courses.module.ts
: Defines the module for courses. -
courses.service.ts
: Business logic for course management. -
entities/
: Contains theCourse
entity that maps to a database table. -
dto/
: DTOs for handling incoming course data (e.g.,create-course.dto.ts
,update-course.dto.ts
).
Lessons Module (lessons/
):
Manages lessons that belong to courses.
-
lessons.controller.ts
: API endpoints for lesson-related operations. -
lessons.module.ts
: Module definition for lessons. -
lessons.service.ts
: Handles lesson logic such as creation and updates. -
entities/
: Defines theLesson
entity. -
dto/
: Data transfer objects for lessons (e.g.,create-lesson.dto.ts
,update-lesson.dto.ts
).
Quizzes Module (quizzes/
):
Handles quizzes, quiz submissions, and results.
-
quizzes.controller.ts
: API endpoints for quizzes (e.g., submitting a quiz, fetching results). -
quizzes.module.ts
: Module for quizzes. -
quizzes.service.ts
: Logic for creating quizzes, handling submissions, and calculating results. -
entities/
: Defines theQuiz
entity. -
dto/
: DTOs for quiz-related operations (e.g.,create-quiz.dto.ts
,submit-quiz.dto.ts
).
Common Module (common/
):
Reusable utilities, decorators, and guards.
-
decorators/
: Custom decorators (e.g.,roles.decorator.ts
for role-based access control). -
guards/
: Auth and role-based guards (jwt-auth.guard.ts
,roles.guard.ts
). -
utils/
: Utility functions such as hashing utilities (hash.util.ts
).
Config Module (config/
):
Holds configuration files, environment variable management, and other setup configurations.
-
app.config.ts
: Application-wide configuration (port, global variables). -
database.config.ts
: Database configuration file for TypeORM connection settings.
Database Module (database/
):
Handles database connection, migrations, and entity definitions.
-
typeorm.config.ts
: Configuration for connecting to PostgreSQL using TypeORM. -
migrations/
: Database migration files.
App Root Files
-
app.module.ts
: Root module of the application. This imports all other modules (Auth, Users, Courses, etc.). -
main.ts
: Entry point for the application. This file bootstraps the NestJS application.
Other Folders
-
prisma/
(optional): If you choose to use Prisma instead of TypeORM, this folder contains the Prisma schema.-
schema.prisma
: Defines Prisma models and relationships.
-
Root-Level Files
-
.env
: Environment variables for sensitive information like the database URL, JWT secret, etc. -
docker-compose.yml
: Optional Docker configuration for setting up PostgreSQL and the backend in Docker containers. -
tsconfig.json
: TypeScript configuration file. -
package.json
: Dependencies, scripts, and metadata for your project.
3. Example Code Snippets
user.entity.ts
(TypeORM Entity Example)
import { Entity, Column, PrimaryGeneratedColumn, OneToMany } from 'typeorm';
import { Course } from '../../courses/entities/course.entity';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({ unique: true })
email: string;
@Column()
password: string;
@OneToMany(() => Course, course => course.user)
courses: Course[];
}
auth.service.ts
(JWT Token Example)
import { Injectable
} from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { UsersService } from '../users/users.service';
import { JwtPayload } from './interfaces/jwt-payload.interface';
@Injectable()
export class AuthService {
constructor(
private readonly usersService: UsersService,
private readonly jwtService: JwtService,
) {}
async validateUser(email: string, password: string): Promise<any> {
const user = await this.usersService.findOneByEmail(email);
// Validate password (e.g., bcrypt)
return user; // Return user if validated
}
async login(user: any) {
const payload: JwtPayload = { email: user.email, sub: user.id };
return {
access_token: this.jwtService.sign(payload),
};
}
}
typeorm.config.ts
(TypeORM Configuration Example)
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
export const typeOrmConfig: TypeOrmModuleOptions = {
type: 'postgres',
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT) || 5432,
username: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
entities: [__dirname + '/../**/*.entity{.ts,.js}'],
synchronize: true, // Disable in production!
};
This NestJS + PostgreSQL folder structure and the configuration is scalable and modular, making it easy to extend as your project grows. Let me know if you'd like additional details or examples!
Here’s the full code implementation for the Next.js folder structure you provided, complete with TypeScript, Tailwind CSS, and various pages for login, registration, dashboard, courses, quizzes, and profile.
Folder Structure Overview
my-language-app/
├── app/
│ ├── (auth)/
│ │ ├── login/
│ │ │ └── page.tsx
│ │ ├── register/
│ │ │ └── page.tsx
│ ├── (dashboard)/
│ │ ├── layout.tsx
│ │ ├── page.tsx
│ ├── courses/
│ │ ├── page.tsx
│ │ ├── [courseId]/
│ │ │ ├── page.tsx
│ │ │ ├── quiz/
│ │ │ │ └── page.tsx
│ ├── profile/
│ │ └── page.tsx
│ ├── layout.tsx
│ ├── page.tsx
1. app/(auth)/login/page.tsx
This page handles the login functionality.
// app/(auth)/login/page.tsx
import { useState } from 'react';
import { useRouter } from 'next/navigation';
export default function LoginPage() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const router = useRouter();
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault();
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
});
if (response.ok) {
router.push('/dashboard');
} else {
alert('Login failed');
}
};
return (
<div className="flex justify-center items-center min-h-screen">
<form onSubmit={handleLogin} className="w-full max-w-sm">
<h2 className="text-2xl font-bold mb-4">Login</h2>
<input
type="email"
className="input"
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<input
type="password"
className="input mt-4"
placeholder="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<button className="btn mt-4" type="submit">Login</button>
</form>
</div>
);
}
2. app/(auth)/register/page.tsx
This page handles the registration process.
// app/(auth)/register/page.tsx
import { useState } from 'react';
import { useRouter } from 'next/navigation';
export default function RegisterPage() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const router = useRouter();
const handleRegister = async (e: React.FormEvent) => {
e.preventDefault();
const response = await fetch('/api/auth/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
});
if (response.ok) {
router.push('/login');
} else {
alert('Registration failed');
}
};
return (
<div className="flex justify-center items-center min-h-screen">
<form onSubmit={handleRegister} className="w-full max-w-sm">
<h2 className="text-2xl font-bold mb-4">Register</h2>
<input
type="email"
className="input"
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<input
type="password"
className="input mt-4"
placeholder="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<button className="btn mt-4" type="submit">Register</button>
</form>
</div>
);
}
3. app/(dashboard)/layout.tsx
Layout for the dashboard and its sub-pages.
// app/(dashboard)/layout.tsx
import { ReactNode } from 'react';
export default function DashboardLayout({ children }: { children: ReactNode }) {
return (
<div className="flex">
<aside className="w-64 h-screen bg-gray-800 text-white">
<nav className="p-4">
<ul>
<li><a href="/dashboard" className="block py-2">Dashboard</a></li>
<li><a href="/courses" className="block py-2">Courses</a></li>
<li><a href="/profile" className="block py-2">Profile</a></li>
</ul>
</nav>
</aside>
<main className="flex-1 p-8">
{children}
</main>
</div>
);
}
4. app/(dashboard)/page.tsx
Dashboard page that users land on after logging in.
// app/(dashboard)/page.tsx
export default function DashboardPage() {
return (
<div>
<h1 className="text-3xl font-bold">Welcome to your Dashboard</h1>
<p className="mt-4">You can view your progress and access courses here.</p>
</div>
);
}
5. app/courses/page.tsx
This page lists all available courses.
// app/courses/page.tsx
import Link from 'next/link';
export default function CoursesPage() {
const courses = [
{ id: 1, title: 'Spanish for Beginners' },
{ id: 2, title: 'French for Beginners' },
{ id: 3, title: 'German for Beginners' },
];
return (
<div>
<h1 className="text-3xl font-bold">Available Courses</h1>
<ul className="mt-4">
{courses.map(course => (
<li key={course.id} className="mt-2">
<Link href={`/courses/${course.id}`}>
<a className="text-blue-500 hover:underline">{course.title}</a>
</Link>
</li>
))}
</ul>
</div>
);
}
6. app/courses/[courseId]/page.tsx
This dynamic page shows details for a specific course.
// app/courses/[courseId]/page.tsx
import { useRouter } from 'next/router';
export default function CourseDetailPage() {
const router = useRouter();
const { courseId } = router.query;
// Replace with actual course data from API
const course = { title: `Course ${courseId}`, description: 'Course description goes here.' };
return (
<div>
<h1 className="text-3xl font-bold">{course.title}</h1>
<p className="mt-4">{course.description}</p>
<a href={`/courses/${courseId}/quiz`} className="text-blue-500 hover:underline mt-4 block">Take Quiz</a>
</div>
);
}
7. app/courses/[courseId]/quiz/page.tsx
Quiz page for the course.
// app/courses/[courseId]/quiz/page.tsx
import { useState } from 'react';
import { useRouter } from 'next/router';
export default function QuizPage() {
const router = useRouter();
const { courseId } = router.query;
const [answers, setAnswers] = useState({ q1: '', q2: '' });
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
// Submit the quiz answers
alert('Quiz submitted!');
};
return (
<div>
<h1 className="text-3xl font-bold">Quiz for Course {courseId}</h1>
<form onSubmit={handleSubmit} className="mt-4">
<div>
<label className="block text-xl">Question 1: Translate "Hello" to Spanish.</label>
<input
type="text"
className="input mt-2"
value={answers.q1}
onChange={(e) => setAnswers({ ...answers, q1: e.target.value })}
/>
</div>
<div className="mt-4">
<label className="block text-xl">Question 2: Translate "Thank you" to Spanish.</label>
<input
type="text"
className="input mt-2"
value={answers.q2}
onChange={(e) => setAnswers({ ...answers, q2: e.target.value })}
/>
</div>
<button className="btn mt-6" type="submit">Submit Quiz</button>
</form>
</div
>
);
}
8. app/profile/page.tsx
User profile page where the user can view and edit their personal details.
// app/profile/page.tsx
export default function ProfilePage() {
return (
<div>
<h1 className="text-3xl font-bold">Your Profile</h1>
<p className="mt-4">Manage your profile details and update your information.</p>
<form className="mt-4">
<div>
<label className="block text-xl">Email</label>
<input type="email" className="input mt-2" value="user@example.com" readOnly />
</div>
<div className="mt-4">
<label className="block text-xl">Change Password</label>
<input type="password" className="input mt-2" placeholder="New Password" />
</div>
<button className="btn mt-6" type="submit">Update Profile</button>
</form>
</div>
);
}
9. app/layout.tsx
Global layout for the entire application.
// app/layout.tsx
import { ReactNode } from 'react';
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="en">
<body className="bg-gray-100">
<header className="bg-blue-600 text-white p-4">
<h1 className="text-2xl">Language Learning App</h1>
</header>
<main className="p-8">{children}</main>
</body>
</html>
);
}
10. app/page.tsx
Home page of the application.
// app/page.tsx
export default function HomePage() {
return (
<div>
<h1 className="text-4xl font-bold">Welcome to the Language Learning App</h1>
<p className="mt-4">Start your language learning journey today!</p>
<a href="/courses" className="text-blue-500 hover:underline mt-4 block">Browse Courses</a>
</div>
);
}
Conclusion
This code provides a complete structure for a Next.js app with pages for login, registration, the dashboard, course listings, quizzes, and a profile page. You can expand these examples further by connecting the pages to a backend API for handling authentication, retrieving courses, and submitting quiz results.
Here's the full implementation for the additional folders and files you mentioned: components, lib, styles, public, and utils. This structure includes reusable components, helper libraries, API handling, styles (using Tailwind CSS), and utility functions for data fetching and constants.
Folder Structure
my-language-app/
├── components/
│ ├── CourseCard.tsx
│ ├── Header.tsx
│ ├── QuizComponent.tsx
├── lib/
│ ├── api.ts
│ ├── auth.ts
├── styles/
│ ├── globals.css
│ └── tailwind.config.js
├── public/
│ ├── logo.png
│ └── favicon.ico
├── utils/
│ ├── fetcher.ts
│ ├── constants.ts
1. components/CourseCard.tsx
The CourseCard
is a reusable component to display information about a course.
// components/CourseCard.tsx
import Link from 'next/link';
interface CourseCardProps {
id: number;
title: string;
description: string;
}
export default function CourseCard({ id, title, description }: CourseCardProps) {
return (
<div className="border rounded-lg p-4 shadow-md hover:shadow-lg transition-shadow">
<h2 className="text-xl font-bold">{title}</h2>
<p className="text-gray-600 mt-2">{description}</p>
<Link href={`/courses/${id}`}>
<a className="text-blue-500 mt-4 block hover:underline">View Course</a>
</Link>
</div>
);
}
2. components/Header.tsx
The Header
component is a global header used throughout the app.
// components/Header.tsx
import Link from 'next/link';
export default function Header() {
return (
<header className="bg-blue-600 text-white p-4">
<div className="container mx-auto flex justify-between items-center">
<Link href="/">
<a className="text-2xl font-bold">Language Learning App</a>
</Link>
<nav>
<ul className="flex space-x-4">
<li><Link href="/courses"><a>Courses</a></Link></li>
<li><Link href="/profile"><a>Profile</a></Link></li>
<li><Link href="/login"><a>Login</a></Link></li>
</ul>
</nav>
</div>
</header>
);
}
3. components/QuizComponent.tsx
The QuizComponent
handles quiz questions and user input.
// components/QuizComponent.tsx
import { useState } from 'react';
interface QuizComponentProps {
question: string;
onSubmit: (answer: string) => void;
}
export default function QuizComponent({ question, onSubmit }: QuizComponentProps) {
const [answer, setAnswer] = useState('');
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
onSubmit(answer);
};
return (
<form onSubmit={handleSubmit}>
<label className="block text-xl">{question}</label>
<input
type="text"
className="input mt-2"
value={answer}
onChange={(e) => setAnswer(e.target.value)}
/>
<button className="btn mt-4" type="submit">
Submit Answer
</button>
</form>
);
}
4. lib/api.ts
The api.ts
file defines API calls for courses and quizzes.
// lib/api.ts
export const getCourses = async () => {
const response = await fetch('/api/courses');
if (!response.ok) {
throw new Error('Failed to fetch courses');
}
return response.json();
};
export const getCourseById = async (id: number) => {
const response = await fetch(`/api/courses/${id}`);
if (!response.ok) {
throw new Error('Failed to fetch course');
}
return response.json();
};
export const submitQuiz = async (courseId: number, answers: any) => {
const response = await fetch(`/api/courses/${courseId}/quiz`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ answers }),
});
if (!response.ok) {
throw new Error('Failed to submit quiz');
}
return response.json();
};
5. lib/auth.ts
The auth.ts
file contains authentication API calls.
// lib/auth.ts
export const login = async (email: string, password: string) => {
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
});
if (!response.ok) {
throw new Error('Login failed');
}
return response.json();
};
export const register = async (email: string, password: string) => {
const response = await fetch('/api/auth/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
});
if (!response.ok) {
throw new Error('Registration failed');
}
return response.json();
};
6. styles/globals.css
The globals.css
file contains global styles for the application, utilizing Tailwind CSS.
/* styles/globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
body {
@apply bg-gray-100 text-gray-800;
}
input {
@apply border rounded-lg p-2 w-full focus:outline-none focus:ring-2 focus:ring-blue-400;
}
.btn {
@apply bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600;
}
7. styles/tailwind.config.js
The tailwind.config.js
file configures Tailwind CSS for the project.
// styles/tailwind.config.js
module.exports = {
content: [
'./app/**/*.{js,ts,jsx,tsx}',
'./components/**/*.{js,ts,jsx,tsx}',
],
theme: {
extend: {},
},
plugins: [],
};
8. public/logo.png
Add your logo image to the public/
folder (e.g., logo.png
).
public/
├── logo.png
└── favicon.ico
9. utils/fetcher.ts
The fetcher.ts
file provides a reusable fetcher function for data fetching, commonly used with SWR or React Query.
// utils/fetcher.ts
export const fetcher = async (url: string) => {
const response = await fetch(url);
if (!response.ok) {
throw new Error('Failed to fetch data');
}
return response.json();
};
10. utils/constants.ts
The constants.ts
file stores constants used throughout the app.
// utils/constants.ts
export const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000';
Conclusion
This structure provides a well-organized and scalable foundation for your Next.js project:
- Reusable Components (CourseCard, Header, QuizComponent) for UI consistency.
- API Handlers (lib/api.ts, lib/auth.ts) for interacting with your backend.
- Global Styling using Tailwind CSS.
-
Utilities like the
fetcher
function and configuration constants to simplify and manage your codebase.
This setup should cover most of the common patterns needed for your full-stack application. Let me know if you need further assistance!
Here's a complete NestJS backend implementation for the folder structure you provided. This includes code for authentication, user management, DTOs, and strategies for handling JWT authentication.
1. Folder Structure Overview
my-language-app-backend/
├── src/
│ ├── auth/
│ │ ├── dto/
│ │ │ ├── login.dto.ts
│ │ │ └── register.dto.ts
│ │ ├── auth.controller.ts
│ │ ├── auth.module.ts
│ │ ├── auth.service.ts
│ │ ├── jwt.strategy.ts
│ │ └── local.strategy.ts
│ ├── users/
│ │ ├── dto/
│ │ │ ├── create-user.dto.ts
│ │ │ ├── update-user.dto.ts
│ │ ├── entities/
│ │ │ └── user.entity.ts
│ │ ├── users.controller.ts
│ │ ├── users.module.ts
│ │ ├── users.service.ts
2. Code Implementation
src/auth/dto/login.dto.ts
This DTO (Data Transfer Object) is used to validate the login request data.
// src/auth/dto/login.dto.ts
import { IsEmail, IsNotEmpty, IsString } from 'class-validator';
export class LoginDto {
@IsEmail()
email: string;
@IsNotEmpty()
@IsString()
password: string;
}
src/auth/dto/register.dto.ts
This DTO is used for user registration. It ensures that the necessary fields are provided.
// src/auth/dto/register.dto.ts
import { IsEmail, IsNotEmpty, IsString } from 'class-validator';
export class RegisterDto {
@IsEmail()
email: string;
@IsNotEmpty()
@IsString()
password: string;
}
src/auth/auth.controller.ts
The AuthController
handles requests for login and registration.
// src/auth/auth.controller.ts
import { Body, Controller, Post } from '@nestjs/common';
import { AuthService } from './auth.service';
import { LoginDto } from './dto/login.dto';
import { RegisterDto } from './dto/register.dto';
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
@Post('login')
async login(@Body() loginDto: LoginDto) {
return this.authService.login(loginDto);
}
@Post('register')
async register(@Body() registerDto: RegisterDto) {
return this.authService.register(registerDto);
}
}
src/auth/auth.module.ts
The AuthModule
bundles the authentication-related providers, controllers, and services.
// src/auth/auth.module.ts
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { UsersModule } from '../users/users.module';
import { JwtStrategy } from './jwt.strategy';
import { LocalStrategy } from './local.strategy';
@Module({
imports: [
UsersModule,
PassportModule,
JwtModule.register({
secret: process.env.JWT_SECRET || 'defaultSecretKey',
signOptions: { expiresIn: '1h' },
}),
],
controllers: [AuthController],
providers: [AuthService, JwtStrategy, LocalStrategy],
})
export class AuthModule {}
src/auth/auth.service.ts
The AuthService
handles the authentication logic. It checks credentials, creates JWT tokens, and handles registration.
// src/auth/auth.service.ts
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { UsersService } from '../users/users.service';
import { LoginDto } from './dto/login.dto';
import { RegisterDto } from './dto/register.dto';
import * as bcrypt from 'bcrypt';
@Injectable()
export class AuthService {
constructor(
private usersService: UsersService,
private jwtService: JwtService,
) {}
async validateUser(email: string, password: string): Promise<any> {
const user = await this.usersService.findByEmail(email);
if (user && bcrypt.compareSync(password, user.password)) {
const { password, ...result } = user;
return result;
}
return null;
}
async login(loginDto: LoginDto) {
const user = await this.validateUser(loginDto.email, loginDto.password);
if (!user) {
throw new UnauthorizedException('Invalid credentials');
}
const payload = { username: user.email, sub: user.id };
return {
access_token: this.jwtService.sign(payload),
};
}
async register(registerDto: RegisterDto) {
const hashedPassword = bcrypt.hashSync(registerDto.password, 10);
return this.usersService.create({
...registerDto,
password: hashedPassword,
});
}
}
src/auth/jwt.strategy.ts
The JwtStrategy
verifies the JWT token and ensures only authenticated users can access protected routes.
// src/auth/jwt.strategy.ts
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: process.env.JWT_SECRET || 'defaultSecretKey',
});
}
async validate(payload: any) {
return { userId: payload.sub, email: payload.username };
}
}
src/auth/local.strategy.ts
The LocalStrategy
is used for basic local authentication (email and password).
// src/auth/local.strategy.ts
import { Strategy } from 'passport-local';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthService } from './auth.service';
@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super({ usernameField: 'email' });
}
async validate(email: string, password: string): Promise<any> {
const user = await this.authService.validateUser(email, password);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
Users Module
src/users/dto/create-user.dto.ts
This DTO handles the creation of new users.
// src/users/dto/create-user.dto.ts
import { IsEmail, IsNotEmpty, IsString } from 'class-validator';
export class CreateUserDto {
@IsEmail()
email: string;
@IsNotEmpty()
@IsString()
password: string;
}
src/users/dto/update-user.dto.ts
This DTO handles updates to user information.
// src/users/dto/update-user.dto.ts
import { IsEmail, IsOptional, IsString } from 'class-validator';
export class UpdateUserDto {
@IsEmail()
@IsOptional()
email?: string;
@IsString()
@IsOptional()
password?: string;
}
src/users/entities/user.entity.ts
The User
entity represents the user model in the database. It's used with TypeORM to map data to the database.
// src/users/entities/user.entity.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({ unique: true })
email: string;
@Column()
password: string;
}
src/users/users.controller.ts
The UsersController
exposes routes for managing users (CRUD operations).
// src/users/users.controller.ts
import { Body, Controller, Get, Param, Patch, Post } from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Post()
create(@Body() createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto);
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.usersService.findOne(+id);
}
@Patch(':id')
update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
return this.usersService.update(+id, updateUserDto);
}
}
src/users/users.module.ts
The UsersModule
registers the service and controller for managing users.
// src/users/users.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
import { User } from './entities/user.entity';
@Module({
imports: [TypeOrmModule.forFeature([User])],
controllers: [Users
Controller],
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}
src/users/users.service.ts
The UsersService
contains the business logic for creating, updating, and retrieving users.
// src/users/users.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { User } from './entities/user.entity';
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private usersRepository: Repository<User>,
) {}
async create(createUserDto: CreateUserDto) {
const user = this.usersRepository.create(createUserDto);
return this.usersRepository.save(user);
}
async findOne(id: number) {
return this.usersRepository.findOne({ where: { id } });
}
async findByEmail(email: string) {
return this.usersRepository.findOne({ where: { email } });
}
async update(id: number, updateUserDto: UpdateUserDto) {
await this.usersRepository.update(id, updateUserDto);
return this.usersRepository.findOne({ where: { id } });
}
}
Conclusion
This NestJS backend setup provides a modular approach to handling authentication and user management using JWT and TypeORM. Here's what it covers:
- Authentication: Using JWT strategy for login and registration.
- User Management: A user service that handles CRUD operations for user data.
- Data Validation: DTOs for validating incoming requests for login, registration, and user updates.
This code is highly extensible and can be integrated with a PostgreSQL database using TypeORM or other compatible database options. Let me know if you need additional features or modifications!
Here's the full NestJS backend code for managing courses and lessons, including DTOs, entities, controllers, modules, and services.
Folder Structure Overview
my-language-app-backend/
├── src/
│ ├── courses/
│ │ ├── dto/
│ │ │ ├── create-course.dto.ts
│ │ │ ├── update-course.dto.ts
│ │ ├── entities/
│ │ │ └── course.entity.ts
│ │ ├── courses.controller.ts
│ │ ├── courses.module.ts
│ │ ├── courses.service.ts
│ ├── lessons/
│ │ ├── dto/
│ │ │ ├── create-lesson.dto.ts
│ │ │ └── update-lesson.dto.ts
│ │ ├── entities/
│ │ │ └── lesson.entity.ts
│ │ ├── lessons.controller.ts
│ │ ├── lessons.module.ts
│ │ ├── lessons.service.ts
1. Courses Module
1.1 src/courses/dto/create-course.dto.ts
This DTO defines the structure for creating a new course.
// src/courses/dto/create-course.dto.ts
import { IsString, IsNotEmpty } from 'class-validator';
export class CreateCourseDto {
@IsString()
@IsNotEmpty()
title: string;
@IsString()
@IsNotEmpty()
description: string;
}
1.2 src/courses/dto/update-course.dto.ts
This DTO defines the structure for updating an existing course.
// src/courses/dto/update-course.dto.ts
import { IsOptional, IsString } from 'class-validator';
export class UpdateCourseDto {
@IsString()
@IsOptional()
title?: string;
@IsString()
@IsOptional()
description?: string;
}
1.3 src/courses/entities/course.entity.ts
The Course
entity maps to the database and represents the structure of a course.
// src/courses/entities/course.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, OneToMany } from 'typeorm';
import { Lesson } from '../../lessons/entities/lesson.entity';
@Entity()
export class Course {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@Column()
description: string;
@OneToMany(() => Lesson, lesson => lesson.course, { cascade: true })
lessons: Lesson[];
}
1.4 src/courses/courses.controller.ts
The CoursesController
handles the incoming HTTP requests for the courses.
// src/courses/courses.controller.ts
import { Body, Controller, Get, Param, Post, Patch, Delete } from '@nestjs/common';
import { CoursesService } from './courses.service';
import { CreateCourseDto } from './dto/create-course.dto';
import { UpdateCourseDto } from './dto/update-course.dto';
@Controller('courses')
export class CoursesController {
constructor(private readonly coursesService: CoursesService) {}
@Get()
findAll() {
return this.coursesService.findAll();
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.coursesService.findOne(+id);
}
@Post()
create(@Body() createCourseDto: CreateCourseDto) {
return this.coursesService.create(createCourseDto);
}
@Patch(':id')
update(@Param('id') id: string, @Body() updateCourseDto: UpdateCourseDto) {
return this.coursesService.update(+id, updateCourseDto);
}
@Delete(':id')
remove(@Param('id') id: string) {
return this.coursesService.remove(+id);
}
}
1.5 src/courses/courses.module.ts
The CoursesModule
imports necessary modules and provides the CoursesService
and CoursesController
.
// src/courses/courses.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { CoursesService } from './courses.service';
import { CoursesController } from './courses.controller';
import { Course } from './entities/course.entity';
@Module({
imports: [TypeOrmModule.forFeature([Course])],
controllers: [CoursesController],
providers: [CoursesService],
exports: [CoursesService],
})
export class CoursesModule {}
1.6 src/courses/courses.service.ts
The CoursesService
contains the business logic for managing courses, such as finding, creating, updating, and deleting courses.
// src/courses/courses.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Course } from './entities/course.entity';
import { CreateCourseDto } from './dto/create-course.dto';
import { UpdateCourseDto } from './dto/update-course.dto';
@Injectable()
export class CoursesService {
constructor(
@InjectRepository(Course)
private coursesRepository: Repository<Course>,
) {}
findAll() {
return this.coursesRepository.find({ relations: ['lessons'] });
}
findOne(id: number) {
return this.coursesRepository.findOne({
where: { id },
relations: ['lessons'],
});
}
create(createCourseDto: CreateCourseDto) {
const course = this.coursesRepository.create(createCourseDto);
return this.coursesRepository.save(course);
}
async update(id: number, updateCourseDto: UpdateCourseDto) {
await this.coursesRepository.update(id, updateCourseDto);
return this.coursesRepository.findOne({ where: { id } });
}
remove(id: number) {
return this.coursesRepository.delete(id);
}
}
2. Lessons Module
2.1 src/lessons/dto/create-lesson.dto.ts
This DTO defines the structure for creating a new lesson.
// src/lessons/dto/create-lesson.dto.ts
import { IsNotEmpty, IsString } from 'class-validator';
export class CreateLessonDto {
@IsString()
@IsNotEmpty()
title: string;
@IsString()
@IsNotEmpty()
content: string;
}
2.2 src/lessons/dto/update-lesson.dto.ts
This DTO defines the structure for updating an existing lesson.
// src/lessons/dto/update-lesson.dto.ts
import { IsOptional, IsString } from 'class-validator';
export class UpdateLessonDto {
@IsString()
@IsOptional()
title?: string;
@IsString()
@IsOptional()
content?: string;
}
2.3 src/lessons/entities/lesson.entity.ts
The Lesson
entity represents the structure of a lesson in the database.
// src/lessons/entities/lesson.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, ManyToOne } from 'typeorm';
import { Course } from '../../courses/entities/course.entity';
@Entity()
export class Lesson {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@Column('text')
content: string;
@ManyToOne(() => Course, course => course.lessons, { onDelete: 'CASCADE' })
course: Course;
}
2.4 src/lessons/lessons.controller.ts
The LessonsController
handles HTTP requests for lessons.
// src/lessons/lessons.controller.ts
import { Body, Controller, Get, Param, Post, Patch, Delete } from '@nestjs/common';
import { LessonsService } from './lessons.service';
import { CreateLessonDto } from './dto/create-lesson.dto';
import { UpdateLessonDto } from './dto/update-lesson.dto';
@Controller('lessons')
export class LessonsController {
constructor(private readonly lessonsService: LessonsService) {}
@Get()
findAll() {
return this.lessonsService.findAll();
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.lessonsService.findOne(+id);
}
@Post()
create(@Body() createLessonDto: CreateLessonDto) {
return this.lessonsService.create(createLessonDto);
}
@Patch(':id')
update(@Param('id') id: string, @Body() updateLessonDto: UpdateLessonDto) {
return this.lessonsService.update(+id, updateLessonDto);
}
@Delete(':id')
remove(@Param('id') id: string) {
return this.lessonsService.remove(+id);
}
}
2.5 src/lessons/lessons.module.ts
The LessonsModule
imports necessary modules and provides the LessonsService
and LessonsController
.
// src/lessons/lessons.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { LessonsService } from './lessons.service';
import { LessonsController } from './lessons.controller';
import { Lesson } from './entities/lesson.entity';
@Module({
imports: [TypeOrm
Module.forFeature([Lesson])],
controllers: [LessonsController],
providers: [LessonsService],
exports: [LessonsService],
})
export class LessonsModule {}
2.6 src/lessons/lessons.service.ts
The LessonsService
contains the business logic for managing lessons.
// src/lessons/lessons.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Lesson } from './entities/lesson.entity';
import { CreateLessonDto } from './dto/create-lesson.dto';
import { UpdateLessonDto } from './dto/update-lesson.dto';
@Injectable()
export class LessonsService {
constructor(
@InjectRepository(Lesson)
private lessonsRepository: Repository<Lesson>,
) {}
findAll() {
return this.lessonsRepository.find({ relations: ['course'] });
}
findOne(id: number) {
return this.lessonsRepository.findOne({
where: { id },
relations: ['course'],
});
}
create(createLessonDto: CreateLessonDto) {
const lesson = this.lessonsRepository.create(createLessonDto);
return this.lessonsRepository.save(lesson);
}
async update(id: number, updateLessonDto: UpdateLessonDto) {
await this.lessonsRepository.update(id, updateLessonDto);
return this.lessonsRepository.findOne({ where: { id } });
}
remove(id: number) {
return this.lessonsRepository.delete(id);
}
}
Conclusion
This implementation covers the Courses and Lessons modules in a NestJS backend. Here's what was included:
- Course Management: CRUD operations for creating, updating, retrieving, and deleting courses.
- Lesson Management: CRUD operations for lessons, each associated with a course.
- DTOs: Data Transfer Objects to validate incoming data.
- Entities: TypeORM entities representing the database schema for both courses and lessons.
- Controllers: HTTP endpoints for managing courses and lessons.
- Services: Business logic for interacting with the database via TypeORM repositories.
This structure is scalable and can be easily extended with more features as needed. Let me know if you need further customization or explanation!
Here is the full implementation for the structure you've outlined, which includes the quizzes module, common decorators, guards, utilities, and config files, as well as the main application setup (app.module.ts
and main.ts
).
Folder Structure Overview
my-language-app-backend/
├── src/
│ ├── quizzes/
│ │ ├── dto/
│ │ │ ├── create-quiz.dto.ts
│ │ │ └── submit-quiz.dto.ts
│ │ ├── entities/
│ │ │ └── quiz.entity.ts
│ │ ├── quizzes.controller.ts
│ │ ├── quizzes.module.ts
│ │ ├── quizzes.service.ts
│ ├── common/
│ │ ├── decorators/
│ │ │ └── roles.decorator.ts
│ │ ├── guards/
│ │ │ ├── jwt-auth.guard.ts
│ │ │ └── roles.guard.ts
│ │ └── utils/
│ │ └── hash.util.ts
│ ├── config/
│ │ ├── app.config.ts
│ │ └── database.config.ts
│ ├── database/
│ │ ├── migrations/
│ │ └── typeorm.config.ts
│ ├── app.module.ts
│ ├── main.ts
1. Quizzes Module
1.1 src/quizzes/dto/create-quiz.dto.ts
This DTO defines the structure for creating a new quiz.
// src/quizzes/dto/create-quiz.dto.ts
import { IsString, IsNotEmpty, IsArray, ValidateNested, IsNumber } from 'class-validator';
import { Type } from 'class-transformer';
class QuizQuestionDto {
@IsString()
@IsNotEmpty()
question: string;
@IsArray()
@IsString({ each: true })
options: string[];
@IsString()
@IsNotEmpty()
correctAnswer: string;
}
export class CreateQuizDto {
@IsString()
@IsNotEmpty()
title: string;
@IsArray()
@ValidateNested({ each: true })
@Type(() => QuizQuestionDto)
questions: QuizQuestionDto[];
}
1.2 src/quizzes/dto/submit-quiz.dto.ts
This DTO defines the structure for submitting a quiz.
// src/quizzes/dto/submit-quiz.dto.ts
import { IsArray, IsString, IsNotEmpty } from 'class-validator';
export class SubmitQuizDto {
@IsArray()
@IsNotEmpty({ each: true })
answers: string[];
@IsString()
@IsNotEmpty()
userId: string;
}
1.3 src/quizzes/entities/quiz.entity.ts
The Quiz
entity defines the schema for the quiz model in the database.
// src/quizzes/entities/quiz.entity.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class Quiz {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@Column('json')
questions: { question: string; options: string[]; correctAnswer: string }[];
}
1.4 src/quizzes/quizzes.controller.ts
The QuizzesController
handles HTTP requests related to quizzes, including creating quizzes, retrieving quizzes, and submitting quiz answers.
// src/quizzes/quizzes.controller.ts
import { Body, Controller, Get, Param, Post } from '@nestjs/common';
import { QuizzesService } from './quizzes.service';
import { CreateQuizDto } from './dto/create-quiz.dto';
import { SubmitQuizDto } from './dto/submit-quiz.dto';
@Controller('quizzes')
export class QuizzesController {
constructor(private readonly quizzesService: QuizzesService) {}
@Get()
findAll() {
return this.quizzesService.findAll();
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.quizzesService.findOne(+id);
}
@Post()
create(@Body() createQuizDto: CreateQuizDto) {
return this.quizzesService.create(createQuizDto);
}
@Post('submit')
submit(@Body() submitQuizDto: SubmitQuizDto) {
return this.quizzesService.submit(submitQuizDto);
}
}
1.5 src/quizzes/quizzes.module.ts
The QuizzesModule
sets up the quizzes module and imports the necessary services and controllers.
// src/quizzes/quizzes.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { QuizzesService } from './quizzes.service';
import { QuizzesController } from './quizzes.controller';
import { Quiz } from './entities/quiz.entity';
@Module({
imports: [TypeOrmModule.forFeature([Quiz])],
controllers: [QuizzesController],
providers: [QuizzesService],
})
export class QuizzesModule {}
1.6 src/quizzes/quizzes.service.ts
The QuizzesService
handles the business logic for managing quizzes, including creating and submitting quizzes.
// src/quizzes/quizzes.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Quiz } from './entities/quiz.entity';
import { CreateQuizDto } from './dto/create-quiz.dto';
import { SubmitQuizDto } from './dto/submit-quiz.dto';
@Injectable()
export class QuizzesService {
constructor(
@InjectRepository(Quiz)
private quizzesRepository: Repository<Quiz>,
) {}
findAll() {
return this.quizzesRepository.find();
}
findOne(id: number) {
return this.quizzesRepository.findOne({ where: { id } });
}
create(createQuizDto: CreateQuizDto) {
const quiz = this.quizzesRepository.create(createQuizDto);
return this.quizzesRepository.save(quiz);
}
submit(submitQuizDto: SubmitQuizDto) {
// Here you'd implement the logic to evaluate the quiz and return a score
// For now, we'll return a simple response
return { message: 'Quiz submitted successfully', score: 80 };
}
}
2. Common Utilities
2.1 src/common/decorators/roles.decorator.ts
The Roles
decorator is used to define role-based access control in your controllers.
// src/common/decorators/roles.decorator.ts
import { SetMetadata } from '@nestjs/common';
export const ROLES_KEY = 'roles';
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);
2.2 src/common/guards/jwt-auth.guard.ts
The JwtAuthGuard
is used to protect routes that require authentication.
// src/common/guards/jwt-auth.guard.ts
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}
2.3 src/common/guards/roles.guard.ts
The RolesGuard
checks whether the user has the required roles to access a route.
// src/common/guards/roles.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ROLES_KEY } from '../decorators/roles.decorator';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.getAllAndOverride<string[]>(ROLES_KEY, [
context.getHandler(),
context.getClass(),
]);
if (!requiredRoles) {
return true;
}
const { user } = context.switchToHttp().getRequest();
return requiredRoles.some((role) => user.roles?.includes(role));
}
}
2.4 src/common/utils/hash.util.ts
Utility to hash passwords (using bcrypt).
// src/common/utils/hash.util.ts
import * as bcrypt from 'bcrypt';
export const hashPassword = async (password: string): Promise<string> => {
const salt = await bcrypt.genSalt();
return bcrypt.hash(password, salt);
};
export const comparePassword = async (
password: string,
hash: string,
): Promise<boolean> => {
return bcrypt.compare(password, hash);
};
3. Config Files
3.1 src/config/app.config.ts
The application-level configuration file for storing environment variables and global app settings.
// src/config/app.config.ts
export const APP_PORT = process.env.APP_PORT || 3000;
export const JWT_SECRET = process.env.JWT_SECRET || 'defaultSecretKey';
3.2 src/config/database.config.ts
The database configuration file for connecting to PostgreSQL or any other TypeORM-supported database.
// src/config/database.config.ts
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
export const databaseConfig: TypeOrmModuleOptions = {
type:
'postgres',
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT, 10) || 5432,
username: process.env.DB_USER || 'postgres',
password: process.env.DB_PASSWORD || 'password',
database: process.env.DB_NAME || 'language-learning',
entities: [__dirname + '/../**/*.entity{.ts,.js}'],
synchronize: true, // Disable in production
};
4. Database Configuration
4.1 src/database/typeorm.config.ts
This is the TypeORM configuration file that loads the database settings.
// src/database/typeorm.config.ts
import { TypeOrmModule } from '@nestjs/typeorm';
import { Module } from '@nestjs/common';
import { databaseConfig } from '../config/database.config';
@Module({
imports: [
TypeOrmModule.forRoot({
...databaseConfig,
}),
],
})
export class DatabaseModule {}
5. Application Setup
5.1 src/app.module.ts
The AppModule
imports all the necessary modules (quizzes, database, etc.) and sets up the main application.
// src/app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { QuizzesModule } from './quizzes/quizzes.module';
import { UsersModule } from './users/users.module'; // Assuming there's a UsersModule
import { AuthModule } from './auth/auth.module'; // Assuming there's an AuthModule
import { DatabaseModule } from './database/typeorm.config';
@Module({
imports: [
ConfigModule.forRoot(), // Loads environment variables
DatabaseModule,
QuizzesModule,
UsersModule,
AuthModule,
],
controllers: [],
providers: [],
})
export class AppModule {}
5.2 src/main.ts
The main.ts
file bootstraps the NestJS application.
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { APP_PORT } from './config/app.config';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(APP_PORT, () => {
console.log(`Application is running on: http://localhost:${APP_PORT}`);
});
}
bootstrap();
Conclusion
This setup includes:
-
Quizzes Module:
- CRUD operations for managing quizzes.
- Quiz submission and validation.
-
Common Utilities:
- Guards for JWT authentication and role-based access control.
- Utility for password hashing.
-
Configuration:
- Database connection using TypeORM with PostgreSQL.
- Application and database configuration files.
-
Application Bootstrap:
- Bootstrapping the NestJS application and integrating with TypeORM for database access.
This code provides a foundation for building a language learning app with quizzes, authentication, and other essential features. Let me know if you need further customization or assistance!
If you enjoy my content and would like to support my work, you can buy me a coffee. Your support is greatly appreciated!
Disclaimer: This content is generated by AI.
Top comments (2)
Very long read...is there a link to the GitHub
As of now, there is no repo.