8000 nextjs typegraphql apollo server micro · wpcodevo/nextjs-typegraphql-api@5427443 · GitHub
[go: up one dir, main page]

Skip to content

Commit 5427443

Browse files
committed
nextjs typegraphql apollo server micro
1 parent 51f6258 commit 5427443

File tree

5 files changed

+315
-1
lines changed

5 files changed

+315
-1
lines changed

src/models/post.model.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import {
2+
getModelForClass,
3+
ModelOptions,
4+
prop,
5+
Severity,
6+
} from '@typegoose/typegoose';
7+
import type { Ref } from '@typegoose/typegoose';
8+
import { User } from './user.model';
9+
10+
@ModelOptions({
11+
schemaOptions: {
12+
timestamps: true,
13+
},
14+
options: {
15+
allowMixed: Severity.ALLOW,
16+
},
17+
})
18+
export class Post {
19+
readonly _id: string;
20+
21+
@prop({ required: true, unique: true })
22+
title: string;
23+
24+
@prop({ required: true })
25+
content: string;
26+
27+
@prop({ required: true })
28+
category: string;
29+
30+
@prop({ default: 'default.jpeg' })
31+
image: string;
32+
33+
@prop({ required: true, ref: () => User })
34+
user: Ref<User>;
35+
}
36+
37+
const PostModel = getModelForClass<typeof Post>(Post);
38+
export default PostModel;

src/resolvers/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
import UserResolver from './user.resolver';
2+
import PostResolver from './post.resolver';
23

3-
export const resolvers = [UserResolver] as const;
4+
export const resolvers = [UserResolver, PostResolver] as const;

src/resolvers/post.resolver.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { Arg, Args, Ctx, Mutation, Query, Resolver } from 'type-graphql';
2+
import {
3+
PostFilter,
4+
PostInput,
5+
PostListResponse,
6+
PostPopulatedResponse,
7+
PostResponse,
8+
UpdatePostInput,
9+
} from '../schemas/post.schema';
10+
import PostService from '../services/post.service';
11+
import type { Context } from '../types/context';
12+
13+
@Resolver()
14+
export default class PostResolver {
15+
constructor(private postService: PostService) {
16+
this.postService = new PostService();
17+
}
18+
19+
@Mutation(() => PostResponse)
20+
async createPost(@Arg('input') input: PostInput, @Ctx() ctx: Context) {
21+
return this.postService.createPost(input, ctx);
22+
}
23+
24+
@Query(() => PostPopulatedResponse)
25+
async getPost(@Arg('id') id: string, @Ctx() ctx: Context) {
26+
return this.postService.getPost(id, ctx);
27+
}
28+
29+
@Mutation(() => PostResponse)
30+
async updatePost(
31+
@Arg('id') id: string,
32+
@Arg('input') input: UpdatePostInput,
33+
@Ctx() ctx: Context
34+
) {
35+
return this.postService.updatePost(id, input, ctx);
36+
}
37+
38+
@Query(() => PostListResponse)
39+
async getPosts(
40+
@Arg('input', { nullable: true }) input: PostFilter,
41+
@Ctx() ctx: Context
42+
) {
43+
return this.postService.getPosts(input, ctx);
44+
}
45+
46+
@Mutation(() => Boolean)
47+
async deletePost(@Arg('id') id: string, @Ctx() ctx: Context) {
48+
return this.postService.deletePost(id, ctx);
49+
}
50+
}

src/schemas/post.schema.ts

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import { MinLength } from 'class-validator';
2+
import { Field, InputType, ObjectType } from 'type-graphql';
3+
import { UserData } from './user.schema';
4+
5+
@InputType()
6+
export class UpdatePostInput {
7+
@MinLength(10, { message: 'Title must be at least 10 characters long' })
8+
@Field(() => String, { nullable: true })
9+
title: string;
10+
11+
@MinLength(10, { message: 'Content must be at least 10 characters long' })
12+
@Field(() => String, { nullable: true })
13+
content: string;
14+
15+
@Field(() => String, { nullable: true })
16+
category: string;
17+
18+
@Field(() => String, { nullable: true })
19+
image: string;
20+
}
21+
22+
@InputType()
23+
export class PostInput {
24+
@MinLength(10, { message: 'Title must be at least 10 characters long' })
25+
@Field(() => String)
26+
title: string;
27+
28+
@MinLength(10, { message: 'Content must be at least 10 characters long' })
29+
@Field(() => String)
30+
content: string;
31+
32+
@Field(() => String)
33+
category: string;
34+
35+
@Field(() => String)
36+
image: string;
37+
}
38+
39+
@InputType()
40+
export class PostFilter {
41+
@Field(() => Number, { nullable: true, defaultValue: 1 })
42+
page: number;
43+
44+
@Field(() => Number, { nullable: true, defaultValue: 10 })
45+
limit: number;
46+
}
47+
48+
@ObjectType()
49+
export class PostDataObj {
50+
@Field(() => String)
51+
readonly _id: string;
52+
53+
@Field(() => String, { nullable: true })
54+
readonly id: string;
55+
56+
@Field(() => String)
57+
title: string;
58+
59+
@Field(() => String)
60+
category: string;
61+
62+
@Field(() => String)
63+
content: string;
64+
65+
@Field(() => String)
66+
image: string;
67+
68+
@Field(() => Date)
69+
createdAt: Date;
70+
71+
@Field(() => Date)
72+
updatedAt: Date;
73+
}
74+
75+
@ObjectType()
76+
export class PostPopulatedData extends PostDataObj {
77+
@Field(() => UserData)
78+
user: UserData;
79+
}
80+
81+
@ObjectType()
82+
export class PostData extends PostDataObj {
83+
@Field(() => String)
84+
user: string;
85+
}
86+
87+
@ObjectType()
88+
export class PostResponse {
89+
@Field(() => String)
90+
status: string;
91+
92+
@Field(() => PostData)
93+
post: PostData;
94+
}
95+
96+
@ObjectType()
97+
export class PostPopulatedResponse {
98+
@Field(() => String)
99+
status: string;
100+
101+
@Field(() => PostPopulatedData)
102+
post: PostPopulatedData;
103+
}
104+
105+
@ObjectType()
106+
export class PostListResponse {
107+
@Field(() => String)
108+
status: string;
109+
110+
@Field(() => Number)
111+
results: number;
112+
113+
@Field(() => [PostPopulatedData])
114+
posts: PostPopulatedData[];
115+
}

src/services/post.service.ts

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { ValidationError } from 'apollo-server-core';
2+
import errorHandler from '../controllers/error.controller';
3+
import deserializeUser from '../middleware/deserializeUser';
4+
import { PostFilter, PostInput } from '../schemas/post.schema';
5+
import PostModel from '../models/post.model';
6+
import { Context } from '../types/context';
7+
8+
export default class PostService {
9+
async createPost(input: Partial<PostInput>, { req, res }: Context) {
10+
try {
11+
const user = await deserializeUser(req, res);
12+
const post = await PostModel.create({ ...input, user: user?._id });
13+
return {
14+
status: 'success',
15+
post: {
16+
...post.toJSON(),
17+
id: post?._id,
18+
},
19+
};
20+
} catch (error: any) {
21+
if (error.code === 11000)
22+
throw new ValidationError('Post with that title already exist');
23+
errorHandler(error);
24+
}
25+
}
26+
27+
async getPost(id: string, { req, res }: Context) {
28+
try {
29+
await deserializeUser(req, res);
30+
const post = await PostModel.findById(id).populate('user').lean();
31+
32+
if (!post) return new ValidationError('No post with that id exists');
33+
34+
return {
35+
status: 'success',
36+
post,
37+
};
38+
} catch (error: any) {
39+
errorHandler(error);
40+
}
41+
}
42+
43+
async updatePost(
44+
id: string,
45+
input: Partial<PostInput>,
46+
{ req, res }: Context
47+
) {
48+
try {
49+
const user = await deserializeUser(req, res);
50+
const post = await PostModel.findByIdAndUpdate(
51+
id,
52+
{ ...input, user: user?._id },
53+
{
54+
new: true,
55+
runValidators: true,
56+
lean: true,
57+
}
58+
);
59+
60+
if (!post) return new ValidationError('No post with that id exists');
61+
return {
62+
status: 'success',
63+
post: {
64+
...post,
65+
id: post?._id,
66+
},
67+
};
68+
} catch (error: any) {
69+
errorHandler(error);
70+
}
71+
}
72+
73+
async getPosts(input: PostFilter, { req, res }: Context) {
74+
try {
75+
const user = await deserializeUser(req, res);
76+
const postsQuery = PostModel.find({ user: user?._id }).populate('user');
77+
78+
// Pagination
79+
const page = input.page || 1;
80+
const limit = input.limit || 10;
81+
const skip = (page - 1) * limit;
82+
83+
const posts = await postsQuery
84+
.sort({ createdAt: -1 })
85+
.skip(skip)
86+
.limit(limit)
87+
.lean();
88+
return {
89+
status: 'success',
90+
results: posts.length,
91+
posts,
92+
};
93+
} catch (error: any) {
94+
errorHandler(error);
95+
}
96+
}
97+
98+
async deletePost(id: string, { req, res }: Context) {
99+
try {
100+
await deserializeUser(req, res);
101+
const post = await PostModel.findByIdAndDelete(id);
102+
103+
if (!post) return new ValidationError('No post with that id exists');
104+
105+
return true;
106+
} catch (error: any) {
107+
errorHandler(error);
108+
}
109+
}
110+
}

0 commit comments

Comments
 (0)
0