Created
October 31, 2024 09:52
-
-
Save deepmtch/b86990721e449e48ea3c0bd9bc4b727d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# Remove existing directory if it exists | |
rm -rf nextjs-graphql-todo | |
# Create project directory | |
mkdir nextjs-graphql-todo | |
cd nextjs-graphql-todo | |
# Initialize Next.js 13 app with TypeScript | |
npx create-next-app@13.5.6 . \ | |
--typescript \ | |
--tailwind \ | |
--eslint \ | |
--app \ | |
--src-dir \ | |
--import-alias "@/*" \ | |
--use-npm | |
# Install dependencies with compatible versions | |
npm install \ | |
@apollo/client@3.8.7 \ | |
@apollo/server@4.9.3 \ | |
@as-integrations/next@2.0.2 \ | |
graphql@16.8.1 \ | |
graphql-tag@2.12.6 \ | |
@prisma/client@5.4.2 \ | |
@apollo/server-plugin-landing-page-graphql-playground@4.0.1 \ | |
--legacy-peer-deps | |
# Install dev dependencies | |
npm install -D \ | |
prisma@5.4.2 \ | |
@types/node@20.8.2 \ | |
@types/react@18.2.25 \ | |
typescript@5.2.2 | |
# Create necessary directories | |
mkdir -p src/app/api/graphql | |
mkdir -p src/graphql | |
mkdir -p src/components | |
mkdir -p src/lib | |
mkdir -p prisma | |
# Create .env file | |
cat > .env << 'EOL' | |
DATABASE_URL="file:./dev.db" | |
EOL | |
# Create .env.local for development | |
cat > .env.local << 'EOL' | |
NODE_ENV=development | |
EOL | |
# Create Prisma schema | |
cat > prisma/schema.prisma << 'EOL' | |
generator client { | |
provider = "prisma-client-js" | |
} | |
datasource db { | |
provider = "sqlite" | |
url = env("DATABASE_URL") | |
} | |
model Todo { | |
id Int @id @default(autoincrement()) | |
title String | |
completed Boolean @default(false) | |
createdAt DateTime @default(now()) | |
updatedAt DateTime @updatedAt | |
} | |
EOL | |
# Create Apollo Client configuration | |
cat > src/lib/apollo-client.ts << 'EOL' | |
import { ApolloClient, InMemoryCache } from '@apollo/client'; | |
export const getClient = () => { | |
return new ApolloClient({ | |
uri: '/api/graphql', | |
cache: new InMemoryCache(), | |
}); | |
}; | |
EOL | |
# Create GraphQL schema | |
cat > src/graphql/schema.ts << 'EOL' | |
import { gql } from 'graphql-tag'; | |
export const typeDefs = gql` | |
type Todo { | |
id: Int! | |
title: String! | |
completed: Boolean! | |
createdAt: String! | |
updatedAt: String! | |
} | |
type Query { | |
todos: [Todo!]! | |
todo(id: Int!): Todo | |
} | |
type Mutation { | |
createTodo(title: String!): Todo! | |
updateTodo(id: Int!, completed: Boolean!): Todo! | |
deleteTodo(id: Int!): Todo! | |
} | |
`; | |
EOL | |
# Create GraphQL resolvers | |
cat > src/graphql/resolvers.ts << 'EOL' | |
import { PrismaClient } from '@prisma/client'; | |
const prisma = new PrismaClient(); | |
export const resolvers = { | |
Query: { | |
todos: async () => { | |
return await prisma.todo.findMany({ | |
orderBy: { createdAt: 'desc' }, | |
}); | |
}, | |
todo: async (_, { id }) => { | |
return await prisma.todo.findUnique({ | |
where: { id }, | |
}); | |
}, | |
}, | |
Mutation: { | |
createTodo: async (_, { title }) => { | |
return await prisma.todo.create({ | |
data: { title }, | |
}); | |
}, | |
updateTodo: async (_, { id, completed }) => { | |
return await prisma.todo.update({ | |
where: { id }, | |
data: { completed }, | |
}); | |
}, | |
deleteTodo: async (_, { id }) => { | |
await prisma.todo.delete({ | |
where: { id }, | |
}); | |
return true; | |
}, | |
}, | |
}; | |
EOL | |
# Create API route with GraphQL Playground | |
cat > src/app/api/graphql/route.ts << 'EOL' | |
import { ApolloServer } from '@apollo/server'; | |
import { startServerAndCreateNextHandler } from '@as-integrations/next'; | |
import { typeDefs } from '@/graphql/schema'; | |
import { resolvers } from '@/graphql/resolvers'; | |
import { NextRequest } from 'next/server'; | |
import { ApolloServerPluginLandingPageLocalDefault } from '@apollo/server/plugin/landingPage/default'; | |
const server = new ApolloServer({ | |
typeDefs, | |
resolvers, | |
introspection: true, | |
plugins: [ | |
ApolloServerPluginLandingPageLocalDefault({ | |
embed: true, | |
includeCookies: true, | |
}), | |
], | |
}); | |
const handler = startServerAndCreateNextHandler(server); | |
export async function GET(request: NextRequest) { | |
return handler(request); | |
} | |
export async function POST(request: NextRequest) { | |
return handler(request); | |
} | |
EOL | |
# Create main page | |
cat > src/app/page.tsx << 'EOL' | |
'use client'; | |
import { useState } from 'react'; | |
import { gql, useQuery, useMutation } from '@apollo/client'; | |
import { ApolloProvider } from '@apollo/client'; | |
import { getClient } from '@/lib/apollo-client'; | |
const GET_TODOS = gql` | |
query GetTodos { | |
todos { | |
id | |
title | |
completed | |
createdAt | |
} | |
} | |
`; | |
const CREATE_TODO = gql` | |
mutation CreateTodo($title: String!) { | |
createTodo(title: $title) { | |
id | |
title | |
completed | |
} | |
} | |
`; | |
const UPDATE_TODO = gql` | |
mutation UpdateTodo($id: Int!, $completed: Boolean!) { | |
updateTodo(id: $id, completed: $completed) { | |
id | |
completed | |
} | |
} | |
`; | |
const DELETE_TODO = gql` | |
mutation DeleteTodo($id: Int!) { | |
deleteTodo(id: $id) | |
} | |
`; | |
function TodoApp() { | |
const [newTodo, setNewTodo] = useState(''); | |
const { data, loading, error, refetch } = useQuery(GET_TODOS); | |
const [createTodo] = useMutation(CREATE_TODO); | |
const [updateTodo] = useMutation(UPDATE_TODO); | |
const [deleteTodo] = useMutation(DELETE_TODO); | |
const handleSubmit = async (e: React.FormEvent) => { | |
e.preventDefault(); | |
if (!newTodo.trim()) return; | |
await createTodo({ | |
variables: { title: newTodo }, | |
}); | |
setNewTodo(''); | |
refetch(); | |
}; | |
const handleToggle = async (id: number, completed: boolean) => { | |
await updateTodo({ | |
variables: { id, completed: !completed }, | |
}); | |
refetch(); | |
}; | |
const handleDelete = async (id: number) => { | |
await deleteTodo({ | |
variables: { id }, | |
}); | |
refetch(); | |
}; | |
if (loading) return <div>Loading...</div>; | |
if (error) return <div>Error: {error.message}</div>; | |
return ( | |
<div className="max-w-2xl mx-auto p-4"> | |
<h1 className="text-3xl font-bold mb-8">Todo App</h1> | |
<form onSubmit={handleSubmit} className="mb-8"> | |
<div className="flex gap-4"> | |
<input | |
type="text" | |
value={newTodo} | |
onChange={(e) => setNewTodo(e.target.value)} | |
placeholder="Add a new todo..." | |
className="flex-1 p-2 border rounded" | |
/> | |
<button | |
type="submit" | |
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600" | |
> | |
Add | |
</button> | |
</div> | |
</form> | |
<div className="space-y-2"> | |
{data?.todos.map((todo: any) => ( | |
<div key={todo.id} className="flex items-center justify-between p-4 border rounded"> | |
<div className="flex items-center gap-4"> | |
<input | |
type="checkbox" | |
checked={todo.completed} | |
onChange={() => handleToggle(todo.id, todo.completed)} | |
className="w-4 h-4" | |
/> | |
<span className={todo.completed ? 'line-through text-gray-500' : ''}> | |
{todo.title} | |
</span> | |
</div> | |
<button | |
onClick={() => handleDelete(todo.id)} | |
className="text-red-500 hover:text-red-700" | |
> | |
Delete | |
</button> | |
</div> | |
))} | |
</div> | |
<div className="mt-8 text-center text-gray-500"> | |
<p>GraphQL Playground available at: <a href="/api/graphql" className="text-blue-500 hover:underline">/api/graphql</a></p> | |
</div> | |
</div> | |
); | |
} | |
export default function Home() { | |
const client = getClient(); | |
return ( | |
<ApolloProvider client={client}> | |
<TodoApp /> | |
</ApolloProvider> | |
); | |
} | |
EOL | |
# Update globals.css | |
cat > src/app/globals.css << 'EOL' | |
@tailwind base; | |
@tailwind components; | |
@tailwind utilities; | |
:root { | |
--foreground-rgb: 0, 0, 0; | |
--background-rgb: 255, 255, 255; | |
} | |
body { | |
color: rgb(var(--foreground-rgb)); | |
background: rgb(var(--background-rgb)); | |
} | |
EOL | |
# Initialize Prisma and create database | |
npx prisma generate | |
npx prisma db push | |
# Add "postinstall" script to package.json | |
npm pkg set scripts.postinstall="prisma generate" | |
echo "Setup complete! Run the following commands to start the app:" | |
echo "npm run dev" | |
echo "" | |
echo "The app will be available at: http://localhost:3000" | |
echo "The GraphQL Playground will be available at: http://localhost:3000/api/graphql" | |
# Fix delete functionality by updating resolver and mutation | |
echo "Fixing delete functionality..." | |
sed -i 's/deleteTodo: async (_, { id }) => {.*return true;/deleteTodo: async (_, { id }) => {\n return await prisma.todo.delete({\n where: { id },\n });/g' src/graphql/resolvers.ts | |
sed -i 's/deleteTodo(id: $id)/deleteTodo(id: $id) {\n id\n }/g' src/app/page.tsx | |
echo "Delete functionality fixed!" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment