├── .eslintrc.json ├── .gitignore ├── README.md ├── app ├── api │ └── graphql │ │ ├── datasources │ │ └── index.ts │ │ ├── models │ │ └── index.ts │ │ ├── resolvers.ts │ │ ├── route.ts │ │ └── schema.ts ├── apollo-client.ts ├── components │ ├── user-badge.tsx │ └── user-form.tsx ├── constants.ts ├── layout.tsx ├── page.tsx └── styles │ └── globals.css ├── next.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── favicon.ico ├── next.svg └── vercel.svg ├── tailwind.config.ts └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.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 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /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/graphql/datasources/index.ts: -------------------------------------------------------------------------------- 1 | // MongoDB Data Source for Users 2 | import UserModel from "../models"; 3 | import { MongoDataSource } from "apollo-datasource-mongodb"; 4 | import { ObjectId } from "mongodb"; 5 | 6 | interface UserDocument { 7 | _id: ObjectId; 8 | username: string; 9 | password: string; 10 | email: string; 11 | interests: [string]; 12 | } 13 | 14 | export default class Users extends MongoDataSource { 15 | // Function to fetch all users 16 | async getAllUsers() { 17 | try { 18 | return await UserModel.find(); 19 | } catch (error) { 20 | throw new Error("Failed to fetch users"); 21 | } 22 | } 23 | 24 | // Function to create a new user 25 | async createUser({ input }: any) { 26 | try { 27 | return await UserModel.create({ ...input }); 28 | } catch (error) { 29 | throw new Error("Failed to create user"); 30 | } 31 | } 32 | 33 | // Function to update existing user 34 | async updateUser({ input }: any) { 35 | try { 36 | const updatedUser = await UserModel.findByIdAndUpdate( 37 | input.id, 38 | { ...input }, 39 | { 40 | new: true, 41 | } 42 | ); 43 | return updatedUser; 44 | } catch (error) { 45 | throw new Error("Failed to update user"); 46 | } 47 | } 48 | 49 | // Function to delete existing user 50 | async deleteUser({ id }: { id: string }): Promise { 51 | try { 52 | await UserModel.findByIdAndDelete(id); 53 | return "User deleted successfully"; 54 | } catch (error) { 55 | throw new Error("Failed to delete user"); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/api/graphql/models/index.ts: -------------------------------------------------------------------------------- 1 | // User Model Creation 2 | const mongoose = require("mongoose"); 3 | const { Schema } = mongoose; 4 | 5 | const userSchema = new Schema({ 6 | // Define user fields here matching the GraphQL schema 7 | first_name: { type: String, required: [true, "All fields are required"] }, 8 | last_name: { 9 | type: String, 10 | required: [true, "All fields are required"], 11 | }, 12 | email: { 13 | type: String, 14 | required: [true, "All fields are required"], 15 | }, 16 | age: { 17 | type: String, 18 | required: [true, "All fields are required"], 19 | }, 20 | active: Boolean, 21 | }); 22 | 23 | export default mongoose.models.UserModel || 24 | mongoose.model("UserModel", userSchema); 25 | -------------------------------------------------------------------------------- /app/api/graphql/resolvers.ts: -------------------------------------------------------------------------------- 1 | const resolvers = { 2 | Query: { 3 | users: async ( 4 | _: any, 5 | __: any, 6 | context: { dataSources: { users: { getAllUsers: () => any } } } 7 | ) => { 8 | try { 9 | return await context.dataSources.users.getAllUsers(); 10 | } catch (error) { 11 | throw new Error("Failed to fetch users"); 12 | } 13 | }, 14 | }, 15 | Mutation: { 16 | createUser: async (_: any, { input }: any, context: any) => { 17 | try { 18 | const newUser = await context.dataSources.users.createUser({ 19 | input, 20 | }); 21 | return newUser; 22 | } catch (error) { 23 | throw new Error("Failed to create user"); 24 | } 25 | }, 26 | updateUser: async (_: any, { input }: any, context: any) => { 27 | try { 28 | return await context.dataSources.users.updateUser({ input }); 29 | } catch (error) { 30 | throw new Error("Failed to update user"); 31 | } 32 | }, 33 | deleteUser: async (_: any, { id }: any, context: any) => { 34 | try { 35 | return await context.dataSources.users.deleteUser({ id }); 36 | } catch (error) { 37 | throw new Error("Failed to delete user"); 38 | } 39 | }, 40 | }, 41 | }; 42 | 43 | export default resolvers; 44 | -------------------------------------------------------------------------------- /app/api/graphql/route.ts: -------------------------------------------------------------------------------- 1 | import { startServerAndCreateNextHandler } from "@as-integrations/next"; 2 | import mongoose from "mongoose"; 3 | import { ApolloServer } from "@apollo/server"; 4 | import { NextRequest } from "next/server"; 5 | import typeDefs from "./schema"; 6 | import resolvers from "./resolvers"; 7 | import Users from "./datasources"; 8 | import UserModel from "./models"; 9 | 10 | const uri = process.env.NEXT_PUBLIC_MONGODB_URI; 11 | 12 | const connectDB = async () => { 13 | try { 14 | if (uri) { 15 | await mongoose.connect(uri); 16 | console.log("🎉 connected to database successfully"); 17 | } 18 | } catch (error) { 19 | console.error(error); 20 | } 21 | }; 22 | connectDB(); 23 | 24 | const server = new ApolloServer({ 25 | resolvers, 26 | typeDefs, 27 | }); 28 | 29 | const handler = startServerAndCreateNextHandler(server, { 30 | context: async (req, res) => ({ 31 | req, 32 | res, 33 | dataSources: { 34 | users: new Users({ modelOrCollection: UserModel }), 35 | }, 36 | }), 37 | }); 38 | export async function GET(request: NextRequest) { 39 | return handler(request); 40 | } 41 | export async function POST(request: NextRequest) { 42 | return handler(request); 43 | } 44 | -------------------------------------------------------------------------------- /app/api/graphql/schema.ts: -------------------------------------------------------------------------------- 1 | const typeDefs = `#graphql 2 | type User { 3 | id: ID! 4 | first_name: String! 5 | last_name: String! 6 | email: String! 7 | age: Int! 8 | active: Boolean 9 | } 10 | 11 | input NewUserInput { 12 | first_name: String! 13 | last_name: String! 14 | email: String! 15 | age: Int! 16 | } 17 | 18 | input UpdateUserInput { 19 | id: ID! 20 | first_name: String 21 | last_name: String 22 | email: String 23 | age: Int 24 | active: Boolean 25 | } 26 | 27 | type Query { 28 | users: [User] 29 | } 30 | 31 | type Mutation { 32 | createUser(input: NewUserInput!): User 33 | updateUser(input: UpdateUserInput!): User 34 | deleteUser(id: ID!): String 35 | } 36 | `; 37 | 38 | export default typeDefs; 39 | -------------------------------------------------------------------------------- /app/apollo-client.ts: -------------------------------------------------------------------------------- 1 | import { ApolloClient, HttpLink, InMemoryCache, from } from "@apollo/client"; 2 | import { onError } from "@apollo/client/link/error"; 3 | 4 | const errorLink = onError(({ networkError, graphQLErrors }) => { 5 | if (graphQLErrors) { 6 | graphQLErrors.forEach(({ message, locations, path }) => { 7 | console.log( 8 | `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}` 9 | ); 10 | }); 11 | if (networkError) console.log(`[Network error]: ${networkError}`); 12 | } 13 | }); 14 | 15 | const httpLink = new HttpLink({ 16 | uri: '/api/graphql' 17 | }) 18 | 19 | const client = new ApolloClient({ 20 | uri: "http://localhost:3000/api/graphql", 21 | cache: new InMemoryCache(), 22 | link: from([errorLink, httpLink]), 23 | }); 24 | 25 | export default client; 26 | -------------------------------------------------------------------------------- /app/components/user-badge.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import { UPDATE_USER } from "../constants"; 3 | import { useMutation } from "@apollo/client"; 4 | 5 | const UserBadge = ({ 6 | user, 7 | refetch, 8 | }: { 9 | user: Record; 10 | refetch: any; 11 | }) => { 12 | const [updateUser] = useMutation(UPDATE_USER); 13 | 14 | const updateActiveStatus = async () => { 15 | await updateUser({ 16 | variables: { input: { id: user?.id, active: user?.active ? false : true } }, 17 | }); 18 | refetch(); 19 | }; 20 | 21 | return ( 22 |
23 |
24 |
25 | 32 |
33 |
34 | 35 | {user?.first_name} {user?.last_name} 36 | 37 | {user?.email} 38 |
39 |
40 | 47 |
48 | ); 49 | }; 50 | 51 | export default UserBadge; 52 | -------------------------------------------------------------------------------- /app/components/user-form.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useMutation } from "@apollo/client"; 4 | import { useForm } from "react-hook-form"; 5 | import { CREATE_USER } from "../constants"; 6 | 7 | const UserForm = ({ refetch }: any) => { 8 | const { 9 | register, 10 | handleSubmit, 11 | reset, 12 | formState: { errors, isSubmitting }, 13 | } = useForm(); 14 | 15 | const [createUser] = useMutation(CREATE_USER); 16 | 17 | const onSubmit = async (data: any) => { 18 | const { age, first_name, last_name, email } = data || {}; 19 | 20 | await createUser({ 21 | variables: { input: { age: Number(age), first_name, last_name, email } }, 22 | }); 23 | refetch(); 24 | reset(); 25 | }; 26 | 27 | return ( 28 |
32 |

33 | Create new user 34 |

35 |
36 | 41 | {errors.first_name && first name is required} 42 |
43 | 44 |
45 | 50 | {errors.last_name && last name is required} 51 |
52 | 53 |
54 | 59 | {errors.email && email is required} 60 |
61 | 62 |
63 | 68 | {errors.age && age is required} 69 |
70 | 71 | 78 |
79 | ); 80 | }; 81 | 82 | export default UserForm; 83 | -------------------------------------------------------------------------------- /app/constants.ts: -------------------------------------------------------------------------------- 1 | import { gql } from "@apollo/client"; 2 | 3 | export const FETCH_USERS = gql` 4 | query getUsers { 5 | users { 6 | id 7 | age 8 | email 9 | first_name 10 | last_name 11 | active 12 | } 13 | } 14 | `; 15 | 16 | export const CREATE_USER = gql` 17 | mutation createUser($input: NewUserInput!) { 18 | createUser(input: $input) { 19 | active 20 | age 21 | email 22 | first_name 23 | id 24 | last_name 25 | } 26 | } 27 | `; 28 | 29 | export const UPDATE_USER = gql` 30 | mutation UpdateUser($input: UpdateUserInput!) { 31 | updateUser(input: $input) { 32 | active 33 | age 34 | email 35 | id 36 | first_name 37 | last_name 38 | } 39 | } 40 | `; 41 | 42 | export const DELETE_USER = gql` 43 | mutation DeleteUser($deleteUserId: ID!) { 44 | deleteUser(id: $deleteUserId) 45 | } 46 | `; 47 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import { Inter } from "next/font/google"; 3 | import { ApolloProvider } from "@apollo/client"; 4 | import client from "./apollo-client"; 5 | 6 | import "./styles/globals.css"; 7 | 8 | const inter = Inter({ subsets: ["latin"] }); 9 | 10 | export default function RootLayout({ 11 | children, 12 | }: { 13 | children: React.ReactNode; 14 | }) { 15 | return ( 16 | 17 | 18 | {children} 19 | 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import UserForm from "./components/user-form"; 3 | import UserBadge from "./components/user-badge"; 4 | import { Fragment } from "react"; 5 | import { useQuery, gql, useMutation } from "@apollo/client"; 6 | import { FETCH_USERS } from "./constants"; 7 | 8 | export default function Home() { 9 | // will update this to fetch users from the db 10 | const { loading, error, data, refetch } = useQuery(FETCH_USERS); 11 | 12 | return ( 13 |
14 | {error &&

Error : {error.message}

} 15 | {loading &&

Loading...

} 16 | 17 |
18 |

list of all users

19 | {data?.users?.map((user: Record) => ( 20 | 21 | 22 | 23 | ))} 24 |
25 |
26 | 27 |
28 |
29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /app/styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | body { 6 | color: rgb(var(--foreground-rgb)); 7 | background: #fafafa 8 | } 9 | 10 | input { 11 | height: 48px; 12 | border-radius: 4px; 13 | padding: 8px; 14 | border: 1px solid #eee; 15 | font-size: 14px; 16 | } 17 | 18 | .input-wrapper { 19 | display: flex; 20 | flex-direction: column; 21 | 22 | } 23 | .input-wrapper span { 24 | color: #ff0000; 25 | font-size: 12px; 26 | } -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {} 3 | 4 | module.exports = nextConfig 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs-user-app", 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 | "@apollo/client": "^3.8.8", 13 | "@apollo/server": "^4.9.5", 14 | "@as-integrations/next": "^3.0.0", 15 | "apollo-datasource-mongodb": "^0.6.0", 16 | "graphql": "^16.8.1", 17 | "mongoose": "^8.0.2", 18 | "next": "14.0.3", 19 | "react": "^18", 20 | "react-dom": "^18", 21 | "react-hook-form": "^7.48.2" 22 | }, 23 | "devDependencies": { 24 | "@types/node": "^20", 25 | "@types/react": "^18", 26 | "@types/react-dom": "^18", 27 | "autoprefixer": "^10.0.1", 28 | "eslint": "^8", 29 | "eslint-config-next": "14.0.3", 30 | "postcss": "^8", 31 | "tailwindcss": "^3.3.0", 32 | "typescript": "^5" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stephany-Doris/NextJS-GraphQL-MongoDB-app/2092864e21d79fe4f91786651b974b9f36d8ffe4/public/favicon.ico -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | --------------------------------------------------------------------------------