Skip to content

Instantly share code, notes, and snippets.

@deepmtch
Created October 31, 2024 09:52
Show Gist options
  • Save deepmtch/b86990721e449e48ea3c0bd9bc4b727d to your computer and use it in GitHub Desktop.
Save deepmtch/b86990721e449e48ea3c0bd9bc4b727d to your computer and use it in GitHub Desktop.
#!/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