Let's outline a plan for building a Node.js GraphQL TypeScript SQLite Vite todo app. I'll break down the key components and considerations, providing a structure you can follow. Since a full, working application would be quite extensive, this will be a guide to get you started.
1. Project Setup:
Bash
npm init vite@latest my-todo-app --template react-ts # Or your preferred React template
cd my-todo-app
npm install graphql @apollo/client graphql-tag typescript @types/graphql @types/node sqlite3 better-sqlite3 # For SQLite
npm install -D nodemon ts-node concurrently # For dev server
2. Backend (Node.js & GraphQL):
server.ts
(orindex.ts
): This will be your main server file.
TypeScript
import express from 'express';
import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@apollo/server/express4';
import cors from 'cors';
import { createSchema } from 'graphql-yoga' // or buildSchema from graphql-tools
import sqlite3 from 'sqlite3';
import { open } from 'sqlite';
// Initialize SQLite database (using better-sqlite3 for type safety)
const dbPromise = open({
filename: './todo.db', // Path to your database file
driver: sqlite3.Database
}).then(async (db) => {
await db.exec(`
CREATE TABLE IF NOT EXISTS todos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
text TEXT NOT NULL,
completed BOOLEAN NOT NULL DEFAULT 0
)
`);
return db;
});
// Define your GraphQL schema (using schema builder or manually)
const typeDefs = /* GraphQL */ `
type Todo {
id: ID!
text: String!
completed: Boolean!
}
type Query {
todos: [Todo!]!
}
type Mutation {
createTodo(text: String!): Todo!
updateTodo(id: ID!, completed: Boolean!): Todo!
deleteTodo(id: ID!): Boolean!
}
`;
const resolvers = {
Query: {
todos: async () => {
const db = await dbPromise;
return await db.all('SELECT * FROM todos');
},
},
Mutation: {
createTodo: async (_, { text }) => {
const db = await dbPromise;
const result = await db.run('INSERT INTO todos (text) VALUES (?)', [text]);
const id = result.lastID;
return { id, text, completed: false };
},
updateTodo: async (_, { id, completed }) => {
const db = await dbPromise;
await db.run('UPDATE todos SET completed = ? WHERE id = ?', [completed, id]);
return await db.get('SELECT * FROM todos WHERE id = ?', [id]);
},
deleteTodo: async (_, { id }) => {
const db = await dbPromise;
await db.run('DELETE FROM todos WHERE id = ?', [id]);
return true;
},
},
};
const schema = createSchema({
typeDefs,
resolvers,
})
const app = express();
app.use(cors()); // Enable CORS for local development
app.use(express.json());
const server = new ApolloServer({
schema,
});
await server.start()
app.use('/graphql', expressMiddleware(server));
const port = 4000;
app.listen(port, () => {
console.log(`Server is running on http://localhost:${port}/graphql`);
});
- Data Access (SQLite): The code above demonstrates basic SQLite integration using
better-sqlite3
which gives type safety. The database connection is handled asynchronously usingopen
from thesqlite
package. Error handling and more robust data access logic would be added in a real application.
3. Frontend (React & Apollo Client):
src/App.tsx
(Example):
TypeScript
import React, { useState, useEffect } from 'react';
import { useQuery, useMutation, gql } from "@apollo/client";
import { client } from './index'; // Apollo Client instance
import { InMemoryCache } from '@apollo/client/cache';
const GET_TODOS = gql`
query GetTodos {
todos {
id
text
completed
}
}
`;
const CREATE_TODO = gql`
mutation CreateTodo($text: String!) {
createTodo(text: $text) {
id
text
completed
}
}
`;
const UPDATE_TODO = gql`
mutation UpdateTodo($id: ID!, $completed: Boolean!) {
updateTodo(id: $id, completed: $completed) {
id
text
completed
}
}
`;
const DELETE_TODO = gql`
mutation DeleteTodo($id: ID!) {
deleteTodo(id: $id)
}
`;
function App() {
const { loading, error, data } = useQuery(GET_TODOS, { client });
const [createTodo] = useMutation(CREATE_TODO, { client, refetchQueries: [GET_TODOS] });
const [updateTodo] = useMutation(UPDATE_TODO, { client, refetchQueries: [GET_TODOS] });
const [deleteTodo] = useMutation(DELETE_TODO, { client, refetchQueries: [GET_TODOS] });
const [newTodoText, setNewTodoText] = useState("");
if (loading) return <p>Loading...</p>;
if (error) return <p>Error : {error.message}</p>;
return (
<div>
<h1>Todo App</h1>
<input type="text" value={newTodoText} onChange={(e) => setNewTodoText(e.target.value)} />
<button onClick={() => {createTodo({ variables: { text: newTodoText } }); setNewTodoText("")}}>Add Todo</button>
<ul>
{data.todos.map((todo) => (
<li key={todo.id}>
<input type="checkbox" checked={todo.completed} onChange={(e) => updateTodo({ variables: { id: todo.id, completed: e.target.checked } })} />
<span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>{todo.text}</span>
<button onClick={() => deleteTodo({ variables: { id: todo.id } })}>Delete</button>
</li>
))}
</ul>
</div>
);
}
export default App;
src/index.tsx
(Apollo Client Setup):
TypeScript
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import './index.css';
import { ApolloClient, InMemoryCache } from "@apollo/client";
import { ApolloProvider } from "@apollo/client";
export const client = new ApolloClient({
uri: 'http://localhost:4000/graphql', // Your GraphQL server endpoint
cache: new InMemoryCache(),
});
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode>
<ApolloProvider client={client}>
<App />
</ApolloProvider>
</React.StrictMode>
);
4. Development:
package.json
(Scripts):
JSON
{
"scripts": {
"dev": "concurrently \"nodemon server.ts\" \"vite\"",
"build": "vite build",
"preview": "vite preview"
}
}
- Run
npm run dev
to start both the server and the Vite development server concurrently.
Key Improvements and Considerations:
- Error Handling: Implement robust error handling in both the backend and frontend.
- Input Validation: Sanitize and validate user input to prevent issues.
- Pagination: For a large number of todos, implement pagination.
- Authentication: If needed, add authentication to protect your API.
- Testing: Write unit and integration tests for both the backend and frontend.
- Deployment: Choose a platform to deploy your application (e.g., Heroku, Netlify, Vercel).
- Database Migrations: Use a tool like Knex.js or TypeORM for database migrations as your schema evolves.
- Type Safety: Leverage TypeScript's features extensively.
- State Management (Frontend): For more complex applications, consider a state management library like Redux or Zustand.
This comprehensive guide provides a solid starting point for your Node.js GraphQL TypeScript SQLite Vite todo app. Remember to install the necessary packages and adapt the code to your
No comments:
Post a Comment