DEV Community

Nadim Chowdhury
Nadim Chowdhury

Posted on

Develop Full Stack Language Learning App in Next JS & Nest JS

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):

  1. Language Courses Overview:
    • Display a list of available language courses.
    • Provide course details with progress tracking for users.
  2. 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.
  3. Progress Tracking:
    • Show completed lessons, total progress, and achievements.
    • Implement a dashboard for users to track their progress.

Backend (API):

  1. User Authentication:
    • Implement authentication using JWT with refresh tokens.
    • Allow users to register, login, reset passwords, and manage profiles.
  2. 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).
  3. Quiz Result Tracking:
    • Save and analyze quiz results for each user.
    • Provide analytics on users' performance in quizzes.
  4. User Profiles:
    • Manage user information, learning history, and preferences.
    • Store progress and completion rates of courses and lessons.

Mobile App (React Native):

  1. Interactive Lessons:
    • Support offline mode for lessons and quizzes.
    • Gamified learning experience with quizzes, drag-and-drop activities, and real-time feedback.
  2. 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
Enter fullscreen mode Exit fullscreen mode
  • 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:

  1. Routing and Pages: Use file-based routing in Next.js for defining pages like courses, lessons, quizzes, and user profiles.
  2. UI/UX: Use Tailwind CSS for styling and responsiveness.
  3. 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

4. Implementation Steps

Step 1: Backend (NestJS) Development

  • Setup NestJS: Use nestjs/cli to scaffold your project:
  nest new language-app-backend
Enter fullscreen mode Exit fullscreen mode
  • Install Dependencies:
  npm install @nestjs/typeorm typeorm pg bcryptjs
  npm install @nestjs/jwt passport-jwt
Enter fullscreen mode Exit fullscreen mode
  • 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
Enter fullscreen mode Exit fullscreen mode
  • Install Tailwind CSS:
  npm install -D tailwindcss postcss autoprefixer
  npx tailwindcss init
Enter fullscreen mode Exit fullscreen mode
  • 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
Enter fullscreen mode Exit fullscreen mode
  • Install Dependencies:
  npm install react-navigation react-native-async-storage
Enter fullscreen mode Exit fullscreen mode
  • Build Screens for lessons, interactive quizzes, and progress tracking.

5. Advanced Features:

  1. Push Notifications: Integrate Firebase for sending push notifications about progress updates or new lessons.
  2. Progress Sync: Ensure users' progress syncs across web and mobile platforms.
  3. 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
Enter fullscreen mode Exit fullscreen mode

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 with SWR or React 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 and app/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 and QuizComponent.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
Enter fullscreen mode Exit fullscreen mode

2. Detailed Explanation

Core Folders

  1. 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, specifically user.entity.ts for defining the User 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 the Course 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 the Lesson 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 the Quiz 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[];
}
Enter fullscreen mode Exit fullscreen mode

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),
    };
  }
}
Enter fullscreen mode Exit fullscreen mode

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!
};
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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

>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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();
};
Enter fullscreen mode Exit fullscreen mode

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();
};
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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: [],
};
Enter fullscreen mode Exit fullscreen mode

8. public/logo.png

Add your logo image to the public/ folder (e.g., logo.png).

public/
├── logo.png
└── favicon.ico
Enter fullscreen mode Exit fullscreen mode

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();
};
Enter fullscreen mode Exit fullscreen mode

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';
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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 {}
Enter fullscreen mode Exit fullscreen mode

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,
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

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 };
  }
}
Enter fullscreen mode Exit fullscreen mode

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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 {}
Enter fullscreen mode Exit fullscreen mode

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 } });
  }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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[];
}
Enter fullscreen mode Exit fullscreen mode

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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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 {}
Enter fullscreen mode Exit fullscreen mode

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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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 {}
Enter fullscreen mode Exit fullscreen mode

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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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[];
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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 }[];
}
Enter fullscreen mode Exit fullscreen mode

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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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 {}
Enter fullscreen mode Exit fullscreen mode

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 };
  }
}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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') {}
Enter fullscreen mode Exit fullscreen mode

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));
  }
}
Enter fullscreen mode Exit fullscreen mode

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);
};
Enter fullscreen mode Exit fullscreen mode

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';
Enter fullscreen mode Exit fullscreen mode

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
};
Enter fullscreen mode Exit fullscreen mode

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 {}
Enter fullscreen mode Exit fullscreen mode

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 {}
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

Conclusion

This setup includes:

  1. Quizzes Module:

    • CRUD operations for managing quizzes.
    • Quiz submission and validation.
  2. Common Utilities:

    • Guards for JWT authentication and role-based access control.
    • Utility for password hashing.
  3. Configuration:

    • Database connection using TypeORM with PostgreSQL.
    • Application and database configuration files.
  4. 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)

Collapse
 
tobidelly profile image
TD!

Very long read...is there a link to the GitHub

Collapse
 
nadim_ch0wdhury profile image
Nadim Chowdhury

As of now, there is no repo.