Free tutorials & notes in Hindi & English · Clean code examples 🧰 मुफ़्त PDF और फ़ोटो टूल्स →
🟣 Data, Server और APIs  ·  Lesson 23

Next.js को MySQL से जोड़ें

Next.js MySQL से कैसे बात करता है

Browserforms, buttons — DB password कभी नहीं देखताNext.js serverServer Components (SELECT) + Server Actions (INSERT/UPDATE/DELETE)lib/db.jsmysql2 connection poolMySQLedusite database
सिर्फ़ server layer database को छूती है

Table बनाइए

SQL – phpMyAdmin या mysql CLI में चलाइए
CREATE DATABASE edusite CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE edusite;

CREATE TABLE courses (
  id         INT AUTO_INCREMENT PRIMARY KEY,
  slug       VARCHAR(80)  NOT NULL UNIQUE,
  title      VARCHAR(120) NOT NULL,
  level      ENUM('Beginner','Intermediate','Advanced') DEFAULT 'Beginner',
  fee        INT NOT NULL DEFAULT 0,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

INSERT INTO courses (slug, title, level, fee) VALUES
('c-programming', 'C Programming', 'Beginner', 2499),
('python-ai', 'Python + AI', 'Beginner', 3499),
('nextjs', 'Next.js', 'Intermediate', 4499);
courses🔑 id INTslug VARCHAR(80) UNIQUEtitle VARCHAR(120)level ENUMfee INTcreated_at TIMESTAMP
courses table

mysql2 install और env vars

Terminal
npm install mysql2 server-only
ENV – .env.local
# .env.local  (never commit this file)
DB_HOST=localhost
DB_USER=edusite_user
DB_PASSWORD=Str0ng#Pass
DB_NAME=edusite

lib/db.js – connection pool

JS – lib/db.js
// lib/db.js
import 'server-only'                     // build error if a client file imports this
import mysql from 'mysql2/promise'

// Re-use one pool across hot reloads in development
const globalForDb = globalThis
export const pool =
  globalForDb.mysqlPool ??
  mysql.createPool({
    host: process.env.DB_HOST,
    user: process.env.DB_USER,
    password: process.env.DB_PASSWORD,
    database: process.env.DB_NAME,
    waitForConnections: true,
    connectionLimit: 5,
  })
if (process.env.NODE_ENV !== 'production') globalForDb.mysqlPool = pool

// Small helper: const rows = await query('SELECT ...', [params])
export async function query(sql, params = []) {
  const [rows] = await pool.execute(sql, params)
  return rows
}

READ: courses की list

JSX – app/admin/courses/page.jsx
// app/admin/courses/page.jsx  (Server Component)
import Link from 'next/link'
import { query } from '@/lib/db'
import { deleteCourse } from './actions'

export const dynamic = 'force-dynamic'   // always show the latest rows

export default async function AdminCourses() {
  const courses = await query('SELECT id, slug, title, level, fee FROM courses ORDER BY id DESC')
  return (
    <main className="container">
      <h1>Courses ({courses.length})</h1>
      <Link href="/admin/courses/new" className="btn">+ Add course</Link>
      <table>
        <thead><tr><th>Title</th><th>Level</th><th>Fee</th><th></th></tr></thead>
        <tbody>
          {courses.map((c) => (
            <tr key={c.id}>
              <td>{c.title}</td><td>{c.level}</td><td>₹{c.fee}</td>
              <td>
                <Link href={`/admin/courses/${c.id}/edit`}>Edit</Link>{' '}
                <form action={deleteCourse} style={{ display: 'inline' }}>
                  <input type="hidden" name="id" value={c.id} />
                  <button>Delete</button>
                </form>
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </main>
  )
}
🔒 localhost:3000/admin/coursesEduSite AdminEnquiriesCoursesCourses (3) [+ Add course]Next.js · Intermediate · ₹4499 Edit DeletePython + AI · Beginner · ₹3499 Edit DeleteC Programming · Beginner · ₹2499 Edit Delete

CREATE: course जोड़ना

JS – app/admin/courses/actions.js
// app/admin/courses/actions.js
'use server'
import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'
import { query } from '@/lib/db'

function readCourse(formData) {
  const title = String(formData.get('title') || '').trim()
  const level = String(formData.get('level') || 'Beginner')
  const fee = Number(formData.get('fee'))
  const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '')
  const errors = {}
  if (title.length < 2) errors.title = 'Title is required'
  if (!['Beginner', 'Intermediate', 'Advanced'].includes(level)) errors.level = 'Invalid level'
  if (!Number.isInteger(fee) || fee < 0) errors.fee = 'Fee must be a whole number'
  return { data: { title, level, fee, slug }, errors }
}

export async function createCourse(prev, formData) {
  const { data, errors } = readCourse(formData)
  if (Object.keys(errors).length) return { errors }
  try {
    await query('INSERT INTO courses (slug, title, level, fee) VALUES (?, ?, ?, ?)',
                [data.slug, data.title, data.level, data.fee])
  } catch (e) {
    if (e.code === 'ER_DUP_ENTRY') return { errors: { title: 'A course with this name exists' } }
    throw e
  }
  revalidatePath('/admin/courses')
  redirect('/admin/courses')
}

export async function updateCourse(id, prev, formData) {   // id bound with .bind()
  const { data, errors } = readCourse(formData)
  if (Object.keys(errors).length) return { errors }
  await query('UPDATE courses SET title = ?, level = ?, fee = ? WHERE id = ?',
              [data.title, data.level, data.fee, id])
  revalidatePath('/admin/courses')
  redirect('/admin/courses')
}

export async function deleteCourse(formData) {
  await query('DELETE FROM courses WHERE id = ?', [Number(formData.get('id'))])
  revalidatePath('/admin/courses')
}
JSX – app/admin/courses/new/page.jsx
// app/admin/courses/new/page.jsx
'use client'
import { useActionState } from 'react'
import { createCourse } from '../actions'

export default function NewCourse() {
  const [state, action, pending] = useActionState(createCourse, { errors: {} })
  const e = state.errors || {}
  return (
    <form action={action} className="container">
      <h1>Add course</h1>
      <input name="title" placeholder="Course title" />  {e.title && <small>{e.title}</small>}
      <select name="level">
        <option>Beginner</option><option>Intermediate</option><option>Advanced</option>
      </select>
      <input name="fee" type="number" placeholder="Fee in ₹" />  {e.fee && <small>{e.fee}</small>}
      <button disabled={pending}>{pending ? 'Saving…' : 'Save course'}</button>
    </form>
  )
}
Admin browserServer ActionMySQLtitle, level, fee submitvalidateINSERT … VALUES (?, ?, ?, ?)OK (insertId)revalidatePath('/admin/courses')redirect → updated list
Create flow

UPDATE और DELETE

JSX – app/admin/courses/[id]/edit/page.jsx
// app/admin/courses/[id]/edit/page.jsx  (Server Component)
import { notFound } from 'next/navigation'
import { query } from '@/lib/db'
import { updateCourse } from '../../actions'

export default async function EditCourse({ params }) {
  const { id } = await params
  const [course] = await query('SELECT * FROM courses WHERE id = ?', [Number(id)])
  if (!course) notFound()
  // bind() pre-fills (id, prevState) so the form only passes formData
  const action = updateCourse.bind(null, course.id, {})
  return (
    <form action={action}>
      <input name="title" defaultValue={course.title} />
      <select name="level" defaultValue={course.level}>
        <option>Beginner</option><option>Intermediate</option><option>Advanced</option>
      </select>
      <input name="fee" type="number" defaultValue={course.fee} />
      <button>Update</button>
    </form>
  )
}

Delete hidden id वाले छोटे form से होता है (ऊपर list page देखें) — JavaScript की ज़रूरत नहीं।

SQL injection – golden rule

JS – placeholders
// ❌ NEVER – user input glued into SQL
query(`SELECT * FROM users WHERE email = '${email}'`)
// email = "' OR '1'='1"  → returns ALL users!

// ✅ ALWAYS – placeholders; mysql2 escapes the values
query('SELECT * FROM users WHERE email = ?', [email])

कौन सी hosting यह support करती है?

ऐसा host चाहिए जो Node.js चलाए और आपके MySQL server तक पहुँच सके: VPS (Hostinger VPS, DigitalOcean), Hostinger के Node.js web apps, Railway, Render, या cloud MySQL (Aiven, TiDB) के साथ Vercel। Plain shared PHP hosting Next.js server नहीं चला सकती। Deploy lesson देखें।

आम गलतियाँ

  • हर function में नया connection बनाना → "Too many connections"। एक pool use करें।
  • .env.local को GitHub पर commit करना → password leak। यह default रूप से .gitignore में है।
  • revalidatePath भूलना → list पुराना data दिखाती रहती है।
  • Vercel पर DB_HOST में localhost → database internet से reachable होना चाहिए।

सारांश

  • lib/db.js में mysql2 pool (server-only), credentials .env.local में।
  • SELECT Server Components में; INSERT/UPDATE/DELETE Server Actions + revalidatePath में।
  • हमेशा ? placeholders use करें।
✅ Practice

enquiries table बनाइए और EduSite contact Server Action को JSON file की जगह MySQL में INSERT करने वाला बनाइए। सारी enquiries /admin/enquiries पर दिखाइए।

अक्सर पूछे जाने वाले प्रश्न (FAQ)

क्या Client Component से MySQL connect कर सकते हैं?
नहीं। Database credentials server पर ही रहने चाहिए। MySQL query सिर्फ़ Server Components, Server Actions या Route Handlers में करें।
Connection pool क्यों use करें?
हर request पर नया MySQL connection खोलना धीमा है और connection limit तक पहुँच सकता है। Pool कुछ connections खुले रखकर उन्हें reuse करता है।
क्या Prisma या Drizzle जैसा ORM use करें?
दोनों Next.js के साथ अच्छे चलते हैं। यह lesson mysql2 के साथ plain SQL use करता है ताकि आप ठीक-ठीक समझें क्या चलता है; बाद में ORM पर जा सकते हैं।
← Next.js Tutorial
🔗

Share this topic with a friend

यह topic किसी दोस्त को भेजें

Found it useful? Send it to a classmate learning the same thing.

अच्छा लगा? जो दोस्त यही सीख रहा है, उसे भेज दीजिए।

अगला topicLogin और Authentication आपकी प्रगति: 23 / 37 (62%) · Lesson 24 / 37 आगे बढ़ें →

यह lesson कैसा लगा?

आपकी राय से हम content और बेहतर बनाते हैं।

अपना review और सुझाव लिखें (वैकल्पिक)

Review approve होने के बाद आपका नाम और शहर इसके साथ public दिख सकते हैं। Email या phone न लिखें।

▶ CodingEasily on YouTube

Video से सीखें – आसान Hindi में

चुनी हुई video: RDBMS भाग 3 – Class XI Commerce IT

▶ Channel subscribe करें