├── .eslintrc.json
├── app
├── favicon.ico
├── layout.tsx
├── globals.css
├── api
│ ├── (dashboard)
│ │ └── notes
│ │ │ ├── [note]
│ │ │ └── route.ts
│ │ │ └── route.ts
│ └── (auth)
│ │ └── users
│ │ └── route.ts
└── page.tsx
├── next.config.mjs
├── postcss.config.js
├── middlewares
└── apis
│ ├── loggingMiddleware.ts
│ └── authMiddleware.ts
├── lib
├── modals
│ ├── notes.ts
│ └── user.ts
└── db.ts
├── .gitignore
├── tailwind.config.ts
├── public
├── vercel.svg
└── next.svg
├── tsconfig.json
├── package.json
├── middleware.ts
└── README.md
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/umairjameel321/nextjs14-restapi/HEAD/app/favicon.ico
--------------------------------------------------------------------------------
/next.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {};
3 |
4 | export default nextConfig;
5 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/middlewares/apis/loggingMiddleware.ts:
--------------------------------------------------------------------------------
1 | export function loggingMiddleware(request: Request) {
2 | return { response: request.method + " " + request.url };
3 | }
4 |
--------------------------------------------------------------------------------
/lib/modals/notes.ts:
--------------------------------------------------------------------------------
1 | import { Schema, model, models } from "mongoose";
2 |
3 | const NoteSchema = new Schema({
4 | title: { type: String, required: true },
5 | description: { type: String },
6 | user: { type: Schema.Types.ObjectId, ref: "User" },
7 | });
8 |
9 | const Note = models.Note || model("Note", NoteSchema);
10 |
11 | export default Note;
12 |
--------------------------------------------------------------------------------
/lib/modals/user.ts:
--------------------------------------------------------------------------------
1 | import { Schema, model, models } from "mongoose";
2 |
3 | const UserSchema = new Schema({
4 | email: { type: String, required: true, unique: true },
5 | username: { type: String, required: true, unique: true },
6 | password: { type: String, required: true },
7 | });
8 |
9 | const User = models.User || model("User", UserSchema);
10 |
11 | export default User;
12 |
--------------------------------------------------------------------------------
/middlewares/apis/authMiddleware.ts:
--------------------------------------------------------------------------------
1 | const validateToken = (token: any) => {
2 | const validToken = true;
3 | if (!token || !validToken) {
4 | return false;
5 | }
6 |
7 | return true;
8 | };
9 |
10 | export function authMiddleware(request: Request): any {
11 | const token = request.headers.get("authorization")?.split(" ")[1];
12 |
13 | return { isValid: validateToken(token) };
14 | }
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 | .yarn/install-state.gz
8 |
9 | # testing
10 | /coverage
11 |
12 | # next.js
13 | /.next/
14 | /out/
15 |
16 | # production
17 | /build
18 |
19 | # misc
20 | .DS_Store
21 | *.pem
22 |
23 | # debug
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | # local env files
29 | .env*.local
30 | .env
31 |
32 | # vercel
33 | .vercel
34 |
35 | # typescript
36 | *.tsbuildinfo
37 | next-env.d.ts
38 |
--------------------------------------------------------------------------------
/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from "next";
2 | import { Inter } from "next/font/google";
3 | import "./globals.css";
4 |
5 | const inter = Inter({ subsets: ["latin"] });
6 |
7 | export const metadata: Metadata = {
8 | title: "Create Next App",
9 | description: "Generated by create next app",
10 | };
11 |
12 | export default function RootLayout({
13 | children,
14 | }: Readonly<{
15 | children: React.ReactNode;
16 | }>) {
17 | return (
18 |
19 |
{children}
20 |
21 | );
22 | }
23 |
--------------------------------------------------------------------------------
/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "tailwindcss";
2 |
3 | const config: Config = {
4 | content: [
5 | "./pages/**/*.{js,ts,jsx,tsx,mdx}",
6 | "./components/**/*.{js,ts,jsx,tsx,mdx}",
7 | "./app/**/*.{js,ts,jsx,tsx,mdx}",
8 | ],
9 | theme: {
10 | extend: {
11 | backgroundImage: {
12 | "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
13 | "gradient-conic":
14 | "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
15 | },
16 | },
17 | },
18 | plugins: [],
19 | };
20 | export default config;
21 |
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": ["dom", "dom.iterable", "esnext"],
4 | "allowJs": true,
5 | "skipLibCheck": true,
6 | "strict": true,
7 | "noEmit": true,
8 | "esModuleInterop": true,
9 | "module": "esnext",
10 | "moduleResolution": "bundler",
11 | "resolveJsonModule": true,
12 | "isolatedModules": true,
13 | "jsx": "preserve",
14 | "incremental": true,
15 | "plugins": [
16 | {
17 | "name": "next"
18 | }
19 | ],
20 | "paths": {
21 | "@/*": ["./*"]
22 | }
23 | },
24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
25 | "exclude": ["node_modules"]
26 | }
27 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rest-apis",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "mongoose": "^8.1.1",
13 | "next": "14.1.0",
14 | "react": "^18",
15 | "react-dom": "^18"
16 | },
17 | "devDependencies": {
18 | "@types/node": "^20",
19 | "@types/react": "^18",
20 | "@types/react-dom": "^18",
21 | "autoprefixer": "^10.0.1",
22 | "eslint": "^8",
23 | "eslint-config-next": "14.1.0",
24 | "postcss": "^8",
25 | "tailwindcss": "^3.3.0",
26 | "typescript": "^5"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | :root {
6 | --foreground-rgb: 0, 0, 0;
7 | --background-start-rgb: 214, 219, 220;
8 | --background-end-rgb: 255, 255, 255;
9 | }
10 |
11 | @media (prefers-color-scheme: dark) {
12 | :root {
13 | --foreground-rgb: 255, 255, 255;
14 | --background-start-rgb: 0, 0, 0;
15 | --background-end-rgb: 0, 0, 0;
16 | }
17 | }
18 |
19 | body {
20 | color: rgb(var(--foreground-rgb));
21 | background: linear-gradient(
22 | to bottom,
23 | transparent,
24 | rgb(var(--background-end-rgb))
25 | )
26 | rgb(var(--background-start-rgb));
27 | }
28 |
29 | @layer utilities {
30 | .text-balance {
31 | text-wrap: balance;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/middleware.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from "next/server";
2 | import { authMiddleware } from "./middlewares/apis/authMiddleware";
3 | import { loggingMiddleware } from "./middlewares/apis/loggingMiddleware";
4 |
5 | export const config = {
6 | matcher: "/api/:path*",
7 | };
8 |
9 | export default function middleware(request: Request) {
10 | if (request.url.includes("/api/notes")) {
11 | const logResult = loggingMiddleware(request);
12 | console.log("Request", logResult.response);
13 | }
14 |
15 | const authResult = authMiddleware(request);
16 | if (!authResult?.isValid) {
17 | return new NextResponse(JSON.stringify({ message: "Unauthorized" }), {
18 | status: 401,
19 | });
20 | }
21 |
22 | return NextResponse.next();
23 | }
24 |
--------------------------------------------------------------------------------
/lib/db.ts:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | const MONGODB_URI = process.env.MONGODB_URI;
4 |
5 | const connect = async () => {
6 | const connectionState = mongoose.connection.readyState;
7 |
8 | if (connectionState === 1) {
9 | console.log("Already connected");
10 | return;
11 | }
12 |
13 | if (connectionState === 2) {
14 | console.log("Connecting...");
15 | return;
16 | }
17 |
18 | try {
19 | mongoose.connect(MONGODB_URI!, {
20 | dbName: "restapinext14",
21 | bufferCommands: false,
22 | });
23 | console.log("Connected");
24 | } catch (error) {
25 | console.log("Error in connecting to database", error);
26 | throw new Error("Error connecting to database");
27 | }
28 | };
29 |
30 | export default connect;
31 |
--------------------------------------------------------------------------------
/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, run the development server:
6 |
7 | ```bash
8 | npm run dev
9 | # or
10 | yarn dev
11 | # or
12 | pnpm dev
13 | # or
14 | bun dev
15 | ```
16 |
17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18 |
19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
20 |
21 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
22 |
23 | ## Learn More
24 |
25 | To learn more about Next.js, take a look at the following resources:
26 |
27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29 |
30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
31 |
32 | ## Deploy on Vercel
33 |
34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
35 |
36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
37 |
--------------------------------------------------------------------------------
/app/api/(dashboard)/notes/[note]/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from "next/server";
2 | import connect from "@/lib/db";
3 | import Note from "@/lib/modals/notes";
4 | import { Types } from "mongoose";
5 | import User from "@/lib/modals/user";
6 |
7 | export const GET = async (request: Request, context: { params: any }) => {
8 | const noteId = context.params.note;
9 | try {
10 | const { searchParams } = new URL(request.url);
11 | const userId = searchParams.get("userId");
12 |
13 | if (!userId || !Types.ObjectId.isValid(userId)) {
14 | return new NextResponse(
15 | JSON.stringify({ message: "Invalid or missing userId" }),
16 | { status: 400 }
17 | );
18 | }
19 |
20 | if (!noteId || !Types.ObjectId.isValid(noteId)) {
21 | return new NextResponse(
22 | JSON.stringify({ message: "Invalid or missing note ID" }),
23 | { status: 400 }
24 | );
25 | }
26 |
27 | await connect();
28 |
29 | // Check if the user exists
30 | const user = await User.findById(userId);
31 | if (!user) {
32 | return new NextResponse(JSON.stringify({ message: "User not found" }), {
33 | status: 404,
34 | });
35 | }
36 |
37 | // Fetch the note and ensure it belongs to the user
38 | const note = await Note.findOne({ _id: noteId, user: userId });
39 | if (!note) {
40 | return new NextResponse(
41 | JSON.stringify({
42 | message: "Note not found or does not belong to the user",
43 | }),
44 | { status: 404 }
45 | );
46 | }
47 |
48 | return new NextResponse(JSON.stringify(note), { status: 200 });
49 | } catch (error) {
50 | return new NextResponse("Error in fetching note " + error, {
51 | status: 500,
52 | });
53 | }
54 | };
55 |
--------------------------------------------------------------------------------
/app/api/(auth)/users/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from "next/server";
2 | import connect from "@/lib/db";
3 | import User from "@/lib/modals/user";
4 | import { Types } from "mongoose";
5 |
6 | const ObjectId = require("mongoose").Types.ObjectId;
7 |
8 | export const GET = async () => {
9 | try {
10 | await connect();
11 | const users = await User.find();
12 | return new NextResponse(JSON.stringify(users), { status: 200 });
13 | } catch (error) {
14 | return new NextResponse("Error in fetching users" + error, { status: 500 });
15 | }
16 | };
17 |
18 | export const POST = async (request: Request) => {
19 | try {
20 | const body = await request.json();
21 |
22 | await connect();
23 | const newUser = new User(body);
24 | await newUser.save();
25 |
26 | return new NextResponse(
27 | JSON.stringify({ message: "User is created", user: newUser }),
28 | { status: 201 }
29 | );
30 | } catch (error) {
31 | return new NextResponse(
32 | JSON.stringify({
33 | message: "Error in creating user",
34 | error,
35 | }),
36 | {
37 | status: 500,
38 | }
39 | );
40 | }
41 | };
42 |
43 | export const PATCH = async (request: Request) => {
44 | try {
45 | const body = await request.json();
46 | const { userId, newUsername } = body;
47 |
48 | await connect();
49 |
50 | if (!userId || !newUsername) {
51 | return new NextResponse(
52 | JSON.stringify({ message: "ID or new username are required" }),
53 | {
54 | status: 400,
55 | }
56 | );
57 | }
58 |
59 | if (!Types.ObjectId.isValid(userId)) {
60 | return new NextResponse(JSON.stringify({ message: "Invalid userId" }), {
61 | status: 400,
62 | });
63 | }
64 |
65 | const updatedUser = await User.findOneAndUpdate(
66 | { _id: new ObjectId(userId) },
67 | { username: newUsername },
68 | { new: true }
69 | );
70 |
71 | if (!updatedUser) {
72 | return new NextResponse(
73 | JSON.stringify({
74 | message: "User not found or didn't update user successfully.",
75 | }),
76 | {
77 | status: 400,
78 | }
79 | );
80 | }
81 |
82 | // Return a success response
83 | return new NextResponse(
84 | JSON.stringify({
85 | message: "Username updated successfully",
86 | user: updatedUser,
87 | }),
88 | {
89 | status: 200,
90 | }
91 | );
92 | } catch (error) {
93 | return new NextResponse(
94 | JSON.stringify({
95 | message: "Error updating username",
96 | error,
97 | }),
98 | {
99 | status: 500,
100 | }
101 | );
102 | }
103 | };
104 |
105 | export const DELETE = async (request: Request) => {
106 | try {
107 | const { searchParams } = new URL(request.url);
108 | const userId = searchParams.get("userId");
109 |
110 | // Validate the userId
111 | if (!userId) {
112 | return new NextResponse(
113 | JSON.stringify({ message: "UserId is required" }),
114 | {
115 | status: 400,
116 | }
117 | );
118 | }
119 |
120 | // Validate if userId is a valid ObjectId
121 | if (!Types.ObjectId.isValid(userId)) {
122 | return new NextResponse(JSON.stringify({ message: "Invalid userId" }), {
123 | status: 400,
124 | });
125 | }
126 |
127 | await connect();
128 |
129 | // TODO
130 |
131 | const deletedUser = await User.findByIdAndDelete(
132 | new Types.ObjectId(userId)
133 | );
134 |
135 | // Check if the user was found and deleted
136 | if (!deletedUser) {
137 | return new NextResponse(JSON.stringify({ message: "User not found" }), {
138 | status: 404,
139 | });
140 | }
141 |
142 | // Return a success response
143 | return new NextResponse(
144 | JSON.stringify({
145 | message: "User deleted successfully",
146 | }),
147 | {
148 | status: 200,
149 | }
150 | );
151 | } catch (error) {
152 | return new NextResponse(
153 | JSON.stringify({
154 | message: "Error deleting user",
155 | error, // Send a user-friendly error message
156 | }),
157 | {
158 | status: 500,
159 | }
160 | );
161 | }
162 | };
163 |
--------------------------------------------------------------------------------
/app/api/(dashboard)/notes/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from "next/server";
2 | import connect from "@/lib/db";
3 | import Note from "@/lib/modals/notes";
4 | import { Types } from "mongoose";
5 | import User from "@/lib/modals/user";
6 |
7 | export const GET = async (request: Request) => {
8 | try {
9 | const { searchParams } = new URL(request.url);
10 | const userId = searchParams.get("userId");
11 |
12 | if (!userId || !Types.ObjectId.isValid(userId)) {
13 | return new NextResponse(
14 | JSON.stringify({ message: "Invalid or missing userId" }),
15 | { status: 400 }
16 | );
17 | }
18 |
19 | await connect();
20 |
21 | const user = await User.findById(userId);
22 | if (!user) {
23 | return new NextResponse(JSON.stringify({ message: "User not found" }), {
24 | status: 404,
25 | });
26 | }
27 |
28 | const notes = await Note.find({ user: new Types.ObjectId(userId) });
29 | return new NextResponse(JSON.stringify(notes), { status: 200 });
30 | } catch (error) {
31 | return new NextResponse("Error in fetching notes" + error, { status: 500 });
32 | }
33 | };
34 |
35 | export const POST = async (request: Request) => {
36 | try {
37 | const { searchParams } = new URL(request.url);
38 | const userId = searchParams.get("userId");
39 |
40 | const body = await request.json();
41 | const { title, description } = body;
42 |
43 | if (!userId || !Types.ObjectId.isValid(userId)) {
44 | return new NextResponse(
45 | JSON.stringify({ message: "Invalid or missing userId" }),
46 | { status: 400 }
47 | );
48 | }
49 |
50 | await connect();
51 |
52 | // Check if the user exists
53 | const user = await User.findById(userId);
54 | if (!user) {
55 | return new NextResponse(JSON.stringify({ message: "User not found" }), {
56 | status: 404,
57 | });
58 | }
59 |
60 | const newNote = new Note({
61 | title,
62 | description,
63 | user: new Types.ObjectId(userId),
64 | });
65 |
66 | await newNote.save();
67 | return new NextResponse(
68 | JSON.stringify({ message: "Note created", note: newNote }),
69 | { status: 201 }
70 | );
71 | } catch (error) {
72 | return new NextResponse(
73 | JSON.stringify({
74 | message: "Error in creating note",
75 | error,
76 | }),
77 | { status: 500 }
78 | );
79 | }
80 | };
81 |
82 | export const PATCH = async (request: Request) => {
83 | try {
84 | const body = await request.json();
85 | const { noteId, title, description } = body;
86 |
87 | const { searchParams } = new URL(request.url);
88 | const userId = searchParams.get("userId");
89 |
90 | if (!noteId || !Types.ObjectId.isValid(noteId)) {
91 | return new NextResponse(
92 | JSON.stringify({ message: "Invalid or missing noteId" }),
93 | { status: 400 }
94 | );
95 | }
96 |
97 | if (!userId || !Types.ObjectId.isValid(userId)) {
98 | return new NextResponse(
99 | JSON.stringify({ message: "Invalid or missing userId" }),
100 | { status: 400 }
101 | );
102 | }
103 |
104 | await connect();
105 |
106 | // Check if the user exists
107 | const user = await User.findById(userId);
108 | if (!user) {
109 | return new NextResponse(JSON.stringify({ message: "User not found" }), {
110 | status: 404,
111 | });
112 | }
113 |
114 | // Find the note and ensure it belongs to the user
115 | const note = await Note.findOne({ _id: noteId, user: userId });
116 | if (!note) {
117 | return new NextResponse(
118 | JSON.stringify({
119 | message: "Note not found or does not belong to the user",
120 | }),
121 | {
122 | status: 404,
123 | }
124 | );
125 | }
126 |
127 | const updatedNote = await Note.findByIdAndUpdate(
128 | noteId,
129 | { title, description },
130 | { new: true }
131 | );
132 |
133 | return new NextResponse(
134 | JSON.stringify({ message: "Note updated", note: updatedNote }),
135 | { status: 200 }
136 | );
137 | } catch (error) {
138 | return new NextResponse(
139 | JSON.stringify({
140 | message: "Error in updating note",
141 | error,
142 | }),
143 | { status: 500 }
144 | );
145 | }
146 | };
147 |
148 | export const DELETE = async (request: Request) => {
149 | try {
150 | const { searchParams } = new URL(request.url);
151 | const noteId = searchParams.get("noteId");
152 | const userId = searchParams.get("userId");
153 |
154 | if (!userId || !Types.ObjectId.isValid(userId)) {
155 | return new NextResponse(
156 | JSON.stringify({ message: "Invalid or missing userId" }),
157 | { status: 400 }
158 | );
159 | }
160 |
161 | if (!noteId || !Types.ObjectId.isValid(noteId)) {
162 | return new NextResponse(
163 | JSON.stringify({ message: "Invalid or missing noteId" }),
164 | { status: 400 }
165 | );
166 | }
167 |
168 | await connect();
169 |
170 | // Check if the user exists
171 | const user = await User.findById(userId);
172 | if (!user) {
173 | return new NextResponse(JSON.stringify({ message: "User not found" }), {
174 | status: 404,
175 | });
176 | }
177 |
178 | // Check if the note exists and belongs to the user
179 | const note = await Note.findOne({ _id: noteId, user: userId });
180 | if (!note) {
181 | return new NextResponse(
182 | JSON.stringify({
183 | message: "Note not found or does not belong to the user",
184 | }),
185 | {
186 | status: 404,
187 | }
188 | );
189 | }
190 |
191 | await Note.findByIdAndDelete(noteId);
192 |
193 | return new NextResponse(
194 | JSON.stringify({ message: "Note deleted successfully" }),
195 | { status: 200 }
196 | );
197 | } catch (error) {
198 | return new NextResponse(
199 | JSON.stringify({
200 | message: "Error in deleting note",
201 | error,
202 | }),
203 | { status: 500 }
204 | );
205 | }
206 | };
207 |
--------------------------------------------------------------------------------
/app/page.tsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 |
3 | export default function Home() {
4 | return (
5 |
6 |
7 |
8 | Get started by editing
9 | app/page.tsx
10 |
11 |
29 |
30 |
31 |
32 |
40 |
41 |
42 |
111 |
112 | );
113 | }
114 |
--------------------------------------------------------------------------------