JSTAcademy
0 XP
Dashboard
Crash Courses
Environment, HTTP & Building Your First API Route
12 min
Basic+150 XP
Crash Courses · Basic

Environment, HTTP & Building Your First API Route

Set up VS Code with a REST client, understand HTTP from first principles, and build a complete Next.js API route.
12 min read+150 XP on completionCert: REST APIs Crash Course
Tap any word in the text below to start reading from there.

Environment, HTTP & Building Your First API Route

Step 1 — Set Up Your Environment

Install a REST client in VS Code

You need a tool to send HTTP requests and inspect responses without writing client code. You have two great options built into VS Code:

Option A: Thunder Client (recommended — GUI like Postman)

  1. In VS Code Extensions, search "Thunder Client" and install it.
  2. Click the thunder bolt icon in the sidebar. Click "New Request."
  3. Set the URL to http://localhost:3000/api/hello and click Send.

Option B: REST Client extension

  1. Install "REST Client" by Huachao Mao.
  2. Create a file called requests.http. Type your request directly:
### Get all courses
GET http://localhost:3000/api/courses
Content-Type: application/json

### Create a course
POST http://localhost:3000/api/courses
Content-Type: application/json

{
  "title": "Test Course",
  "track": "crash",
  "xp": 150
}

Click "Send Request" above each block to fire it. Responses appear in a split pane.

Create a Next.js project (if you don't have one)

npx create-next-app@latest my-api-project --typescript --app
cd my-api-project
npm run dev

What IS an HTTP Request? — First Principles

Every time a browser loads a page or your JavaScript calls an API, it sends an HTTP request and receives an HTTP response. Understanding this lifecycle makes everything else make sense.

An HTTP request has four parts:

  1. Method — What operation? GET, POST, PATCH, DELETE
  2. URL — What resource? https://api.example.com/courses/cc-postgres-m01
  3. Headers — Metadata: content type, auth token, accepted formats
  4. Body — Data payload (POST/PATCH requests only — GET has no body)

An HTTP response has three parts:

  1. Status code — What happened? 200, 201, 400, 404, 500
  2. Headers — Metadata: content type, cache policy, CORS
  3. Body — The returned data (JSON, HTML, empty)

The full lifecycle:

Client                              Server
  |                                    |
  |-- GET /api/courses HTTP/1.1 -----> |
  |   Host: localhost:3000             |
  |   Authorization: Bearer eyJ...     |
  |                                    |
  |                          [query DB]|
  |                          [build JSON]
  |                                    |
  | <-- HTTP/1.1 200 OK -------------- |
  |     Content-Type: application/json |
  |     { "data": [...] }              |

REST URL Design — Resources, Not Verbs

REST organizes URLs around resources (nouns), not actions (verbs). The HTTP method describes the action.

WRONG (verbs in URL):
GET  /api/getCourses
POST /api/createCourse
POST /api/deleteCourse?id=123

RIGHT (resources + methods):
GET    /api/courses           list all courses
POST   /api/courses           create a course
GET    /api/courses/:id       get one course
PATCH  /api/courses/:id       update a course
DELETE /api/courses/:id       delete a course
GET    /api/users/:id/courses  courses for a user (nested resource)

HTTP Methods Mapped to CRUD

GET     → Read       → safe, idempotent, no body
POST    → Create     → not idempotent (creates new each time)
PUT     → Replace    → full replace, idempotent
PATCH   → Update     → partial update (send only changed fields)
DELETE  → Remove     → idempotent (deleting twice = same result)

HTTP Status Codes — What They Mean

2xx Success:
  200 OK           — successful GET, PATCH, PUT
  201 Created      — successful POST (new resource was created)
  204 No Content   — successful DELETE (no body returned)

4xx Client Error:
  400 Bad Request  — malformed request or missing required fields
  401 Unauthorized — not authenticated (no token or invalid token)
  403 Forbidden    — authenticated but no permission
  404 Not Found    — that resource does not exist
  422 Unprocessable — valid format but fails validation rules
  429 Too Many Requests — rate limited

5xx Server Error:
  500 Internal Server Error — something went wrong on the server
  503 Service Unavailable   — server is down or overloaded

Build a Complete Next.js API Route

// src/app/api/courses/route.ts

import { NextRequest, NextResponse } from 'next/server'

// In-memory "database" for this example
const courses = [
  { id: 'cc-postgres-m01', title: 'SQL Fundamentals', track: 'crash', xp: 150 },
  { id: 'cc-restapi-m01',  title: 'REST Fundamentals', track: 'crash', xp: 150 },
]

// GET /api/courses — list all courses, optional ?track= filter
export async function GET(req: NextRequest) {
  const track = req.nextUrl.searchParams.get('track')
  const result = track ? courses.filter(c => c.track === track) : courses

  return NextResponse.json({ data: result, total: result.length })
  // Returns 200 OK automatically
}

// POST /api/courses — create a new course
export async function POST(req: NextRequest) {
  try {
    const body = await req.json()

    if (!body.title || !body.track) {
      return NextResponse.json(
        { error: 'title and track are required' },
        { status: 400 }
      )
    }

    const newCourse = { id: crypto.randomUUID(), ...body }
    courses.push(newCourse)

    return NextResponse.json({ data: newCourse }, { status: 201 })
    // 201 Created — not 200, because a new resource was created
  } catch {
    return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 })
  }
}
// src/app/api/courses/[id]/route.ts
import { NextRequest, NextResponse } from 'next/server'

export async function GET(
  req: NextRequest,
  { params }: { params: Promise<{ id: string }> }
) {
  const { id } = await params
  const course = courses.find(c => c.id === id)

  if (!course) {
    return NextResponse.json({ error: 'Course not found' }, { status: 404 })
  }

  return NextResponse.json({ data: course })
}

export async function DELETE(
  req: NextRequest,
  { params }: { params: Promise<{ id: string }> }
) {
  const { id } = await params
  const index = courses.findIndex(c => c.id === id)

  if (index === -1) {
    return NextResponse.json({ error: 'Course not found' }, { status: 404 })
  }

  courses.splice(index, 1)
  return new NextResponse(null, { status: 204 })  // 204 No Content
}

Test these routes in Thunder Client or your .http file — use the correct method (GET vs POST) and send JSON in the body for POST requests.

0%