Skip to main content

Todo App with Next.js App Router and Mercury API

Let's craft a Todo App with Next.js App Router and Mercury API, incorporating features like custom lists, completed tasks, and user-specific access control.

1. Setting Up Your Next.js Project

Start by scaffolding a Next.js project with the App Router enabled:

npx create-next-app@latest --experimental-app

2. Install Mercury and Dependencies

Integrate Mercury into your project:

npm install @mercury-js/core mongoose graphql graphql-middleware @apollo/server @as-integrations/next @graphql-tools/schema

3. Project Structure

Organize your project with a focus on clarity:

todo-app/
app/
api/
mercury/
route.ts
(your page components)
models/
index.ts
Todo.model.ts
TodoList.model.ts
profiles/
index.ts
User.profile.ts
hooks/
index.ts
Todo.hook.ts

4. Crafting the Mercury API (route.ts)

  • Import and Connect:
import mercury from '@mercury-js/core';
import { startServerAndCreateNextHandler } from '@as-integrations/next';
import { ApolloServer } from '@apollo/server';
import { makeExecutableSchema } from '@graphql-tools/schema';
import { applyMiddleware } from 'graphql-middleware';
import './models';
import './profiles';
import './hooks';

mercury.connect(process.env.DB_URL || 'mongodb://localhost:27017/mercury');
  • Set up the GraphQL Server:
const schema = applyMiddleware(
makeExecutableSchema({
typeDefs: mercury.typeDefs,
resolvers: mercury.resolvers as unknown as IResolvers<
any,
GraphQLResolveInfo
>[],
})
);

const server = new ApolloServer({
schema,
});

const handler = startServerAndCreateNextHandler(server, {
context: async (req, res) => ({
...req,
user: {
id: '1', // Replace with actual user authentication logic
profile: 'User',
},
}),
});

export async function GET(request: any) {
return handler(request);
}

export async function POST(request: any) {
return handler(request);
}

5. Define Data Models (models/)

  • Todo.model.ts
import mercury from '@mercury-js/core';

export const Todo = mercury.createModel(
'Todo',
{
title: { type: 'string', required: true },
description: { type: 'string' },
completed: { type: 'boolean', default: false },
list: {
type: 'relationship',
ref: 'TodoList',
required: true,
},
createdBy: {
type: 'relationship',
ref: 'User',
autopopulate: true,
},
},
{
timestamps: true,
}
);
  • TodoList.model.ts
import mercury from '@mercury-js/core';

export const TodoList = mercury.createModel(
'TodoList',
{
name: { type: 'string', required: true },
isCustom: { type: 'boolean', default: false },
createdBy: {
type: 'relationship',
ref: 'User',
autopopulate: true,
},
},
{
timestamps: true,
}
);
  • index.ts
export { Todo } from './Todo.model';
export { TodoList } from './TodoList.model';

6. Establish User Profiles (profiles/)

  • User.profile.ts
import mercury from '@mercury-js/core';

const rules = [
{
modelName: 'Todo',
access: {
create: true,
read: true,
update: true,
delete: true,
},
// Only allow access to own todos
recordLevelAccess: true,
getRecordQuery: (user) => ({ createdBy: user.id }),
},
{
modelName: 'TodoList',
access: {
create: true,
read: true,
update: true,
delete: true,
},
// Only allow access to own lists
recordLevelAccess: true,
getRecordQuery: (user) => ({ createdBy: user.id }),
},
];

export const UserProfile = mercury.createProfile('User', rules);
  • index.ts
export { UserProfile } from './User.profile';

7. Implement Hooks for Custom Logic (hooks/)

  • Todo.hook.ts
import { hook } from '@mercury-js/core';

// Example: Prevent completing todos in a custom list
hook.before('UPDATE_TODO_RECORD', async function (this: any) {
if (this.data.completed && this.record.list.isCustom) {
throw new Error('Cannot complete todos in a custom list');
}
});
  • index.ts
export { default as TodoHook } from './Todo.hook';

8. Build Your Next.js Pages

  • Leverage Apollo Client to interact with your Mercury-powered GraphQL API.
  • Structure your app with components for:
    • Todo list display
    • Adding new todos
    • Marking todos as complete
    • Creating and managing custom lists
    • Viewing completed tasks

Example Page Component (using react-query for data fetching)

'use client';

import { useQuery, useMutation } from 'react-query';
import { gql } from '@apollo/client';
import { mercuryInstance } from '@/app/api/mercury/route';

const GET_TODOS = gql`
query GetTodos {
listTodo {
_id
title
completed
}
}
`;

const Todos = () => {
const { data, isLoading, error } = useQuery('todos', async () => {
const result = await mercuryInstance.graphql(GET_TODOS);
return result.data.listTodo;
});

if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;

return (
<ul>
{data.map((todo) => (
<li key={todo._id}>
{todo.title} - {todo.completed ? 'Completed' : 'Pending'}
</li>
))}
</ul>
);
};

export default Todos;

Remember

  • Replace placeholders ('1') with your actual user authentication logic.
  • Tailor your frontend components and styling to your preferences.
  • Consider additional features like filtering, sorting, and due dates.
  • Explore Mercury's extensive capabilities (virtual fields, plugins) for advanced use cases.