-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Service Classes (e.g. for NestJS) #5273
Comments
Ok, regarding Nr. 2 from my post above. const typeCreateArgs = `Prisma.${type}CreateArgs`;
const typeFindManyArgs = `Prisma.${type}FindManyArgs`;
// ... Unfortunately you're not able to use these variable names as generics, like so: export UserService extends CrudService<
type,
typeDelegate
typeCreateArgs,
typeFindManyArgs,
// ...
> {
// ...
} again, they cannot be created in the All the best |
Thanks @johannesschobel! We'll have a look how to make this possible. |
Dear @timsuchanek , thank you very much for getting back to me with this issue. The solution would certainly be a bit prettier, if However, for now, let me explain my current solution to the problem: Solution to create CRUD Services:1. Create a
|
Thanks for this proposal! To understand better why you're reaching for the repository pattern, do you mind sharing what you need to do in your RESTful API controllers that's leading to this boilerplate? An example of the controllers with prisma would be really helpful. |
Dear @matthewmueller , In the API i would need to create a lot of controllers, that expose One controller class may look like this: import { Controller, Get, Post, Patch, Delete } from '@nestjs/common';
import { UserService } from './services/user.service.ts';
@Controller('users')
export class UserController {
constructor(private readonly service: UserService) {}
@Get(':id')
async getOne(@Param('id') id: string): string {
return await this.service.findUnique({where: {id: id}})
}
@Post()
async create(@Body() data: CreateUserRequest) {
const dto: Prisma.UserCreateArgs = {
data: { /* map from request to dto */ }
};
return await this.service.create(dto);
}
// other API endpoints for update, delete, getMany, ...
} The import { Injectable } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { CrudService } from './services/crud.service';
import { PrismaService } from './services/prisma.service';
import { UserTypeMap } from './builders/user-map.builder';
@Injectable()
export class UserService extends CrudService<
Prisma.UserDelegate,
UserTypeMap
> {
constructor(private readonly prisma: PrismaService) {
super(prisma.user);
}
// maybe additional (custom) methods that may be specific to methods of the exposed
// controller (i.e., get assigned Groups for a particular User).
} All these CRUD methods within the I am pretty sure that the controller itself may also be created in a generic way, so that you can omit those basic CRUD method declarations. However, i do consider this as "out of scope" for prisma, because it depends on the underlying framework (in my case Does this help you? |
Dear @matthewmueller and @timsuchanek , You can find my repository here: Showcase RepositoryIn this repository i added 2 additional models ( Finally, in each module there is a folder called Note that there would be the possibility to remove this "boilerplate" code in the All the best |
update for the latest 2.16.0 versionI just updated to the latest prisma version. The code still works, but it lot weirder to "properly" type methods because of the newly method signatures in the autogenerated PrismaClient. |
My absolutely typesafe soluition for this problem
so we can create classes like this for model Category
|
For the moment I use this snippet of code to have a UserService with nestjs: @Module({
providers: [
{
provide: 'USER_SERVICE',
useFactory: (databaseService: DatabaseService) => {
return databaseService.user;
},
inject: [DatabaseService]
}],
})
export class UserModule {} But I did not found how to properly and elegantly extend it |
A I'm just getting started with Typescript and Prisma, and spent the day floundering around attempting to achieve this on my own |
Indeed, reusability in Prisma should be easier. 👍 To add some context, I'm trying to reuse in every models, a pagination system. export interface PaginationOptions<T> {
model: T
query: {
limit: number
before?: number
after?: number
}
}
export const pagination = async <T>(
options: PaginationOptions<T>
): Promise<T[]> => {
const { model, query } = options
// @ts-expect-error
const items = await model.findMany({
take: query.before != null ? query.limit * -1 : query.limit,
skip: query.after != null || query.before != null ? 1 : undefined,
...(query.after != null && {
cursor: {
id: query.after
}
}),
...(query.before != null && {
cursor: {
id: query.before
}
})
})
return items
} It works, but it is ugly as the Currently to reuse the logic there is a "workaround", you can check a discussion about this there : #7075 |
It will be nice to get a OpenAPI Specification generated for the prisma schema file so that all of the excellent tooling available in OpenApi Tools can be leveraged |
Dear @VinayaSathyanarayana , If you are using additional API Specifications like JSON:API (https://jsonapi.org/) this adds additional constraints regarding the structure of the input / output resources (i.e., every attribute must be wrapped in a I guess, the best solution would be to add a dedicated package which transforms the model to openapi specifications. But from my perspective, this has nothing to do with the default prisma package |
Thanks @johannesschobel - The converter/generator can be dedicated package. Having the ability to generate a OpenAPI Spec will be of great use |
I also believe that this is a really important missing feature as also discussed in:
I find it necessary in order to keep the DRY principle. However unless I am missing something, the currently proposed workarounds are not fully-type-safe, meaning that they only have the effect of the Typescript compiler not complaining about unknown types but at the same time lose the strict typing of every generated type. For example accepting a plain object in If I am right about this, then it kind of takes away the advertised advantage of Prisma against TypeORM for explicit type safety of the returned and queried fields. I believe the only way to properly support this, is if the compiler produces all the necessary output supporting conditional typing, where the proper types should be mapped to a generic interface. For example by supplying the An other way could be if a Nested Type structure would be the output of the compiler, where IndexedAccess is supported in Typescript and selecting on demand the appropriate interface to implement for each Service - Class. |
Is this has offcial support or solution now? |
I've found these helpful Edit: I think I found an open issue related to what I'm looking for: |
The more I work with in-real-life problems with Prisma, the more I'm starting to realise a CRUD wrapper is not something Prisma core will want to support. I think it's something that will need to be crowd supported. But having worked in Open Source for many years in the past, not having some sort of endorsement from the parent project is always problematic. So I wonder if Prisma would entertain community ideas, and if there is enough hands willing to support them in the community, they'd provide a repo under the Prisma org, and the project could enjoy a level of semi-official status. In terms of implementation, and unless you consider only the most vanilla of cases, the CRUD wrapper is very tricky (not to mention the added dimension of using workspaces in a mono-repo). I find myself having to jump into raw sql more and more to support language features that Prisma does not. But I'd be interested in sharing my experiences with it, if others were interested. However, I don't think it's possible to organise everything just in "this thread" if that's the way people wanted to go. Thanks in advance. |
for this issue, i think we're not going to have the wrapper since in regarding adding all the types to the classes, |
Funny, I was trying to also make a CRUD service just now and I started going about looking for a Delegate base interface to base things off of when I found this thread. I would also greatly appreciate some way to extend that interface. That being said, I'm not smart enough to implement that 😅. Is there no way to implement / extend the models that are generated in a generic manner as to create an execution point for business logic? I've been looking through individual solutions on a case by case basis for most of the Prisma apps I work on, but now I'm here lol. |
I think a unified model (allowing for read-write instance out of the gate) will be based on something like the following (if one is aiming for a TypeORM-ish experience): import { Message, PrismaClient } from '.prisma/client'
class AbstractPrismaModel<T, P> {
constructor(protected dbReader: P, protected dbWriter: P) {
}
public async count(): Promise<number> {
return this.reader().count()
}
public async findMany():Promise<T[]> { /* ... */ }
public async createOne(data: Partial<T>): Promise<T> {
return this.writer().create({ data: data as any } // <-- Haven't found a way to type this easily yet
}
abstract protected reader(): Todo {}
abstract protected writer(): Todo {}
}
class MessageModel extends AbstractPrismaModel<Message, PrismaClient>() {
protected reader() {
return this.dbReader.message
}
protected writer() {
return this.dbWriter.message
}
} Obviously a lot of typings to sort out, and also allowing for where conditions, etc. |
@janpio thanks for reaching out. The problem I think most of us are trying to solving is that, anecdotally, a lot of us are coming from Sequelise or Waterline or TypeORM, and we are used to having a CRUD model that we can extend from. Prisma presents some challenges with the typings, specifically I believe with the In Prisma, to make a model I'd currently construct each data service/model by hand (which is why several people go down the generator route). import { PrismaClient, Thing } from '.prisma/client'
class ThingService {
constructor(private db: PrismaClient) {}
public async findMany(): Promise<Thing[]> {
return this.db.thing.findMany()
}
// repeat for each separate database operation I need, `count`, `update`, `upsert` etc and so on.
} This results in a lot of code duplication. What I'd like to do instead is have a base to extend off, something like: import { GenericDelegate, TheGenericArgsForFindMany } from 'prisma'
class MyBasePrismaService<E> {
construtor(private repo: GenericDelegate<E>) {}
public async findMany(args: TheGenericArgsForFindMany<E>): Promise<E[]> {
return this.delegate.findMany(args)
}
} And then I can implement a concrete class like so: import { Thing } from '.prisma/client'
class MyThingService extends MyBasePrismaService<Thing> {
} Conceptually anyway. I guess the first thing I'd ask is how is Prisma intending us to build our data service layers when we have a non-trivial amount of entities to deal with and the amount of effort to explicitly re-implement (coding and unit tests) every CRUD operation is non-trivial? I can abstract my CRUD controllers very easily (usually they are just the How does Prisma see us keeping those service layers DRY? Perhaps a better question is how are you keeping your own projects DRY? Or to put it another way, is there a way that additional typings can be exposed so that someone can publish their own CRUD wrapper on npm and we can just extend it with our own import { Thing } from '.prisma/client'
import { PrismaCrudModel } from 'awesome-prisma-crud'
class ThingCrudService extends PrismaCrudModel<Thing> {
} Thanks in advance. |
That's what we need! I think and try so many ways but no one can solve the problem, So now we turn back to typeORM already. LOL |
No, because I love the Prisma migrations too much to let it go, LOLs :) |
Hello there 👋 Just chiming in here with a similar use-case. I'm not (yet) in need of CRUD wrappers, but I do need a way to extend the built-in methods while keeping them type-safe. |
Hey everyone, I am excited to share some news on this and I am looking for your feedback. We have a proposal for extending the Prisma Client, and this feature request was part of our research. I propose to solve this via a generic extension: const prisma = new PrismaClient().$extend({
$model: {
$all: {
getClass<T extends object>(this: T): new () => T {
return class { /** Generic Implementation */ } as any
}
},
},
})
class UserService extends prisma.user.getClass() {
method() {
const user = this.findFirst({}) // fully typesafe
}
} Let me know if this works for your use-case and I'd love to get your feedback on the Prisma Client Extensions proposal. |
Absolutely love this @millsp and think it would be a great addition!
I might leave a comment on that proposal about other aspects. |
Dear all, for those who are working with Install it via npm i -D @prisma-utils/prisma-crud-generator The package allows for generating a fully typesafe crud service for the models that are defined in the Simply add the generator to your npx prisma generate voila - it creates fully usable stubs for the CRUD generator for you. Don't like the stub that i added to the lib? Easy switch it out with your own custom stub (i.e., different variables, more methods, ...). See the readme file for more configuration options on the generator. Oh, and by the way.. All the best |
Thanks, @johannesschobel for the solution for creating a wrapper function for Prisma But your solution does not give type hint of the return type of wrapper methods const User = new UserModel()
User
.findAll({})
.then((data) => {
console.log(data)
})
.catch((err) => console.error(err)) just update for solution and this is my solution for create wrapper import { PrismaClient } from '@prisma/client'
import { Prisma } from '@prisma/client'
const prisma = new PrismaClient()
interface DbMethods {
findFirst(data: unknown): Promise<unknown>
findUnique(data: unknown): Promise<unknown>
findMany(data: unknown): Promise<unknown>
create(data: unknown): Promise<unknown>
createMany(data: unknown): Promise<unknown>
update(data: unknown): Promise<unknown>
updateMany(data: unknown): Promise<unknown>
delete(data: unknown): Promise<unknown>
deleteMany(data: unknown): Promise<unknown>
}
interface DbTypeMap{
findSingle:unknown,
findSingleReturn:unknown,
findUnique: unknown,
findUniqueReturn: unknown,
findAll: unknown,
findAllReturn:unknown,
create:unknown,
createReturn:unknown,
createAll:unknown,
createAllReturn:unknown,
updateSingle: unknown,
updateSingleReturn: unknown,
updateAll:unknown,
updateAllReturn:unknown,
delete:unknown,
deleteReturn:unknown,
deleteMany:unknown,
deleteManyReturn:unknown,
}
abstract class DbService<Db extends DbMethods, T extends DbTypeMap> {
constructor(protected db: Db) {}
findSingle(data: T['findSingle']):T['findSingleReturn'] {
return this.db.findFirst(data)
}
findUnique(data:T['findUnique']):T['findUniqueReturn'] {
return this.db.findUnique(data)
}
findAll(data: T['findAll']):T['findAllReturn'] {
return this.db.findMany(data)
}
create(data:T['create']):T['createAllReturn'] {
return this.db.create(data)
}
createAll(data:T['createAll']):T['createAllReturn'] {
return this.db.createMany(data)
}
updateSingle(data:T['updateSingle']):T['updateSingleReturn'] {
return this.db.update(data)
}
updateAll(data:T['updateAll']):T['updateAllReturn'] {
return this.db.updateMany(data)
}
delete(data:T['delete']):T['deleteReturn'] {
return this.db.delete(data)
}
deleteMany(data:T['deleteMany']):T['deleteManyReturn'] {
return this.db.deleteMany(data)
}
}
type UserType = typeof prisma.user
interface UserTypeMap extends DbTypeMap {
findSingle:Prisma.UserFindFirstArgs
findSingleReturn:ReturnType<UserType['findFirst']>,
findUnique:Prisma.UserFindUniqueArgs
findUniqueReturn:ReturnType<UserType['findUnique']>,
findAll:Prisma.UserFindManyArgs
findAllReturn: ReturnType<UserType['findMany']>
create:Prisma.UserCreateArgs,
createReturn:ReturnType<UserType['create']>,
createAll:Prisma.UserCreateManyArgs,
createAllReturn:ReturnType<UserType['createMany']>,
updateSingle: Prisma.UserUpdateArgs,
updateSingleReturn: ReturnType<UserType['update']>,
updateAll:Prisma.UserUpdateManyArgs,
updateAllReturn:ReturnType<UserType['updateMany']>,
delete:Prisma.UserDeleteArgs,
deleteReturn:ReturnType<UserType['delete']>,
deleteMany:Prisma.UserDeleteManyArgs,
deleteManyReturn:ReturnType<UserType['deleteMany']>,
}
class UserModel extends DbService<UserType, UserTypeMap> {
constructor() {
super(prisma.user)
}
}
const User = new UserModel()
User
.findAll({})
.then((data) => {
console.log(data)
})
.catch((err) => console.error(err)) |
Hi everyone 🙂 Before I start, sorry for spamming - I tried to write on the official Prisma slack channel and they redirected me here. 🙂 Similarly, like everyone here I wanted to create a Generic repo layer - I want to create one class with my custom implementations of The idea is to not repeat ourselves (therefore stop violating the DRY principle) and to have one implementation of these methods and respected models will just reuse the same logic from the generic repository. Example:
Basically, in the pseudocode above, just check the comment with the ISSUE tag, that is the main question here - How to get to the point where I can use generics in order to use Prisma types/methods/etc and have full type safety? 🙂 Thanks in advance! |
Dear @stek93 , you can try my library here: https://github.com/prisma-utils/prisma-utils/tree/main/libs/prisma-crud-generator Give it a try ;) |
Inspired by @SahasSaurav and @johannesschobel solutions, I came up with a quite succinct solution of my own with the help of generics. type Operations =
| 'aggregate'
| 'count'
| 'create'
| 'createMany'
| 'delete'
| 'deleteMany'
| 'findFirst'
| 'findMany'
| 'findUnique'
| 'update'
| 'updateMany'
| 'upsert';
export class PrismaRepo<
D extends { [K in Operations]: (args: unknown) => unknown },
A extends { [K in Operations]: unknown },
R extends { [K in Operations]: unknown },
> {
constructor(protected model: D) {}
async findUnique(args: A['findUnique']): Promise<R['findUnique']> {
return this.model.findUnique(args);
}
async findFirst(args: A['findFirst']): Promise<R['findFirst']> {
return this.model.findFirst(args);
}
async findMany(args: A['findMany']): Promise<R['findMany']> {
return this.model.findMany(args);
}
async create(args: A['create']): Promise<R['create']> {
return this.model.create(args);
}
async createMany(args: A['createMany']): Promise<R['createMany']> {
return this.model.createMany(args);
}
async update(args: A['update']): Promise<R['update']> {
return this.model.update(args);
}
async delete(args: A['delete']): Promise<R['delete']> {
return this.model.delete(args);
}
async upsert(args: A['upsert']): Promise<R['upsert']> {
return this.model.upsert(args);
}
async count(args: A['count']): Promise<R['count']> {
return this.model.count(args);
}
async aggregate(args: A['aggregate']): Promise<R['aggregate']> {
return this.model.aggregate(args);
}
async deleteMany(args: A['deleteMany']): Promise<R['deleteMany']> {
return this.model.deleteMany(args);
}
async updateMany(args: A['updateMany']): Promise<R['updateMany']> {
return this.model.updateMany(args);
}
} export type DelegateArgs<T> = {
[K in keyof T]: T[K] extends (args: infer A) => Promise<unknown> ? A : never;
};
export type DelegateReturnTypes<T> = {
[K in keyof T]: T[K] extends (args: infer A) => Promise<infer R> ? R : never;
}; import { Prisma, PrismaClient } from '@prisma/client';
import { PrismaRepo } from '../../shared/prisma/PrismaRepo';
import { DelegateArgs, DelegateReturnTypes } from '../../shared/prisma/types';
type MarketDelegate = Prisma.MarketDelegate<unknown>;
export class MarketRepo extends PrismaRepo<
MarketDelegate,
DelegateArgs<MarketDelegate>,
DelegateReturnTypes<MarketDelegate>
> {
constructor() {
super(new PrismaClient().market);
}
} Result: I'm not sure about the significance of |
Just made an alternative of my previous approach on regarding this issue, my first library that can be use is prisma-repo but we need to re run the script every time we create a new migrations files and the other one is @krsbx/prisma-repo which will re-run every time we create a new migrations since it's a prisma generator I recommend to use the one with prisma generator since it's much faster than the one that read the definition that can be unreliable when we use a different output file destination for the prisma client generator |
@filipkrw Good solutions I really like that you decrease the line of code with mapped type and infer keyword when you will create a transaction with prisma it required to have Prisma Promise Prisma Methods return Prisma Promise which is wrapper around the promise. See the error reference below down |
took inspiration from these solutions #5273 (comment) and #5273 (comment) import { PrismaClient } from '@prisma/client'
import type { Prisma } from '@prisma/client'
const prisma = new PrismaClient()
type Operation =
| 'findFirst'
| 'findUnique'
| 'findMany'
| 'create'
| 'createMany'
| 'update'
| 'updateMany'
| 'delete'
| 'deleteMany'
| 'count'
abstract class DbService<
Db extends { [Key in Operation]: (data: any) => unknown },
Args extends { [K in Operation]: unknown },
Return extends { [K in Operation]: unknown }
> {
constructor(protected db: Db) {}
findFirst(data?: Args['findFirst']): Return['findFirst'] {
return this.db.findFirst(data)
}
findUnique(data: Args['findUnique']): Return['findUnique'] {
return this.db.findUnique(data)
}
findMany(data?: Args['findMany']): Return['findMany'] {
return this.db.findMany(data)
}
create(data: Args['create']): Return['create'] {
return this.db.create(data)
}
createMany(data: Args['createMany']): Return['createMany'] {
return this.db.createMany(data)
}
update(data: Args['update']): Return['update'] {
return this.db.update(data)
}
updateMany(data: Args['updateMany']): Return['updateMany'] {
return this.db.updateMany(data)
}
delete(data: Args['delete']): Return['delete'] {
return this.db.delete(data)
}
deleteMany(data?: Args['deleteMany']): Return['deleteMany'] {
return this.db.deleteMany(data)
}
count(data?: Args['count']): Return['count'] {
return this.db.count(data)
}
}
type DelegateArgs<T> = {
[Key in keyof T]: T[Key] extends (args: infer A) => unknown ? A : never
}
type DelegateReturnTypes<T> = {
[Key in keyof T]: T[Key] extends (...args: any[]) => any ? ReturnType<T[Key]> : never
}
type UserDelegate = Prisma.UserDelegate<Prisma.RejectOnNotFound | Prisma.RejectPerOperation | undefined>
class UserModel extends DbService<UserDelegate, DelegateArgs<UserDelegate>, DelegateReturnTypes<UserDelegate>> {
constructor() {
super(prisma.user)
}
}
const User = new UserModel()
async function main() {
const userList = await User.findFirst()
} |
@SahasSaurav your Args and ReturnTypes are not connected, meaning if you do |
Hi there, based on @SahasSaurav latest solution, the return type is incorrect when we use |
@darr1s same question |
📦 Repository Approach: Simplified Yet Effective To address this, I've found a straightforward solution that closely aligns with the principles of the repository pattern. While not adhering strictly to the traditional repository structure, it effectively achieves the primary objectives of decoupling code from infrastructure code (an essential consideration given that the Prisma client is autogenerated. A sudden change in a model name could potentially break your entire codebase and your IDE can't help you to apply your changes.) Additionally, this solution segregates general functionalities (e.g., findUnique, findAll) from domain-specific operations. 📂 Repository Code: import { Injectable } from '@nestjs/c mon';
import { Prisma, UserDelegate } from '@prisma/client';
import { PrismaService } from 'nestjs-prisma';
import { EmailXORPhone } from '../auth/dto/EmailXORPhone';
@Injectable()
export class UserService {
private readonly m: UserDelegate;
constructor(private readonly prisma: PrismaService) {
this.m = prisma.user;
}
get model() {
return this.m;
}
async findByEmailXorPhone(emailOrPhone: EmailXORPhone) {
return this.model.findUnique({
where: emailOrPhone,
});
}
} 📋 Client Code: export class AuthService {
constructor(
private readonly passwordService:PasswordService,
private readonly userService:UserService
) {
}
async register(emailOrPhone: EmailXORPhone, info: RegisterDto): Promise<RegisterOutput> {
const result = await this.userService.findByEmailXorPhone(emailOrPhone);
if (!result) {
const code = this.passwordService.getRandomReferral();
const password = this.passwordService.getRandomPassword();
return this.userService.model.create({
data: {
...emailOrPhone,
...info,
code,
password,
},
select: {
id: true,
code: true,
},
});
} else {
throw new Error(this.i18nService.t("user already registered."));
}
}
} 🛡️ This approach with minimum redundant code without any compromising in type safety, empowers you to define a controlled policy in your code, restricting direct access to the Prisma client across your codebase, except within repositories. Additionally, within each repository, you can exclusively query or perform actions on its corresponding model (along with its relationships), ensuring a structured and secure data access pattern. |
I've try on this in Prisma 4.x but failed Right not I see Prisma go to 5.x and try again. It working as my expected but not perfectly (not clean code 😢) You can try on it and give another better way to enhance this case import { Prisma } from '@prisma/client'
import { PrismaService } from './prisma.service'
export abstract class PrismaRepository<T extends Prisma.ModelName> {
constructor(
protected readonly prisma: PrismaService,
protected readonly model: Lowercase<T>,
) {}
findMany(
input: Prisma.TypeMap['model'][T]['operations']['findMany']['args'],
): Promise<Prisma.TypeMap['model'][T]['operations']['findMany']['result']> {
// It intersection type
// return this.prisma[this.model].findMany(input)
return (this.prisma[this.model] as unknown as any).findMany(input)
}
}
export class UserService extends PrismaRepository<'User'> {
constructor(protected readonly prisma: PrismaService) {
super(prisma, 'user')
}
}
export class UserController {
constructor(private readonly service: UserService) { }
getAll() {
return this.service.findMany()
}
} |
One thing to keep in mind is that Repository pattern is a design pattern that isolates your data layer from the rest of the app. So when we do things like: class UsersRepository {
create (input: Prisma.UserCreateInput) {
return Prisma.user.create(input);
}
} Is already anti-pattern. Why? Because now, any other APIs in our app that uses this repository, is directly tied to Prisma. If later down the line when we decide to switch from Prisma to something else, any APIs calling type BaseModel = {
id: string;
createAt: Date;
updatedAt: Date;
deletedAt?: Date;
};
type UserModel = BaseModel & {
name: string;
email: string;
};
type CompanyModel = BaseModel & {
name: string;
};
type CreateCompanyInput = {
name: string;
};
type CreateUserInput = {
name: string;
email: string;
} & (
| {
connectCompanyId?: string;
createCompany?: never;
}
| {
connectCompanyId?: never;
createCompany?: CreateCompanyInput | CreateCompanyInput[];
}
);
class UsersRepository {
async create (input: CreateUserInput) {
const { connectCompanyId, createCompany, ..._input } = input;
const result = await Prisma.user.create({
..._input,
company: connectCompanyId
? {
connect: {
id: connectCompanyId,
},
}
: createCompany
? {
create: createCompany,
}
: undefined,
});
return result as unknown as UserModel;
}
} At least this is how I understood the point of repository pattern. My observation is prisma doesn't seem to be very well suited for this because prisma generates everything for us, Models, Inputs, etc, so we will be forced to do things like |
Hello, @lytaitruong. Have you improved this code somehow or do you use it like that still ? I like this approach, but if you look at the return types the |
I'm coming from another language and I'm new to Prisma and Typescript, so I scratched out some half-way pseudo code. But I ask myself if implementing the Repository pattern could work like this in Typescript. Would be lovely to hear feedback for improvements: import type { models } from "$lib/sequelize";
import type { playerAttributes } from "$lib/models/player";
type PlayerId = playerAttributes["id"];
type PlayerData = playerAttributes;
type Player = typeof models.player;
interface IPlayerRepositoryInterface {
getAllPlayers(): Promise<PlayerData[]>;
getAllPlayersPaginated(): Promise<PlayerData[]>;
getAllPlayersPartiallyCached(): Promise<Partial<PlayerData[]>>;
getPlayerById(player_id: PlayerId): Promise<PlayerData | null>;
createPlayer(player_details: Partial<PlayerData>, user_id: number, actionlog_summary: string): Promise<PlayerId>;
updatePlayer(player_id: PlayerId, new_player_details: Partial<PlayerData>, user_id: number, actionlog_summary: string): Promise<boolean>;
deletePlayer(player_id: PlayerId, user_id: number, actionlog_summary: string): Promise<boolean>;
}
export default class PlayerRepository implements IPlayerRepositoryInterface {
constructor(private readonly player_model: Player) { }
async getAllPlayers(): Promise<PlayerData[]> {
return this.player_model.findAll();
}
async getAllPlayersPaginated(): Promise<PlayerData[]> {
throw new Error("Method not implemented.");
}
async getAllPlayersPartiallyCached(): Promise<Partial<PlayerData[]>> {
return this.player_model.findAll({ attributes: ["id", "name"] })
}
async getPlayerById(player_id: PlayerId): Promise<PlayerData | null> {
return this.player_model.findByPk(player_id);
}
createPlayer(player_details: Partial<PlayerData>, user_id: number, actionlog_summary: string): Promise<PlayerId> {
throw new Error("Method not implemented.");
}
updatePlayer(player_id: PlayerId, new_player_details: Partial<PlayerData>, user_id: number, actionlog_summary: string): Promise<boolean> {
throw new Error("Method not implemented.");
}
deletePlayer(player_id: PlayerId, user_id: number, actionlog_summary: string): Promise<boolean> {
throw new Error("Method not implemented.");
}
}
export class MockPlayerRepository implements IPlayerRepositoryInterface {
constructor(/* empty */) { }
async getAllPlayers(): Promise<PlayerData[]> {
return [{ id: 1, name: "Test", country_id: 123 } as PlayerData, { id: 2, name: "Test2", country_id: 123 } as PlayerData];
}
async getAllPlayersPaginated(): Promise<Partial<PlayerData[]>> {
throw new Error("Method not implemented.");
}
async getAllPlayersPartiallyCached(): Promise<PlayerData[]> {
return [{ id: 1, name: "Test", country_id: 123 } as PlayerData, { id: 2, name: "Test2", country_id: 123 } as PlayerData];
}
async getPlayerById(player_id: PlayerId): Promise<PlayerData | null> {
return { id: player_id, name: "Test", country_id: 123 } as PlayerData;
}
createPlayer(player_details: Partial<PlayerData>, user_id: number, actionlog_summary: string): Promise<PlayerId> {
return Promise.resolve(1);
}
updatePlayer(player_id: PlayerId, new_player_details: Partial<PlayerData>, user_id: number, actionlog_summary: string): Promise<boolean> {
return Promise.resolve(false);
}
deletePlayer(player_id: PlayerId, user_id: number, actionlog_summary: string): Promise<boolean> {
return Promise.resolve(true);
}
} |
Check this implementation: #3929 (comment) |
FYI: I renamed this issue to something that hopefully describes better what it is about. The previous name was very broad, but it seems to be specific for service classes effectively. Let me know if someone disagrees with the new title (optimally with an alternative suggestion). |
Dear Prisma Team,
for my upcoming project, i would like to use Prisma, since it is ready to be used in production. I have been around for a year or so, but now finally use it in a concrete project. I really like what you've been working on - Prisma looks great and i can't wait to try it out.
Problem
In the context of my project i will be building a RESTful API with
NestJS
. Unfortunately, because of various reasons I cannot rely on GraphQL, for example, existing 3rd party clients are not able to "speak" (i.e., work with) GraphQL.In order to reduce boilerplate code in my
Service
s, i thought it may be a good idea to create some kind of genericCrudService
that offers basic functionality, likecreate
,update
, ... as some kind of wrapper aroundprisma
. Having usedtypeorm
in projects before, i thought that this may be an easy task. However, i quickly hit some roadblocks, because there are noRepositories
in Prisma like in typeorm.The next idea was to simply inject (i.e., pass) the corresponding
Delegate
to theCrudService
.The closest i could get, however, is like this:
and then create a concrete
UserService
like this:While this works, it has a few drawbacks:
modelDelegate
methods (i.e,create()
,update()
,findMany()
, ...) are unknown, because thedelegate
is not knownUserService
with all its generics looks very uglySuggested solution
Regarding the drawbacks discussed earlier, i would suggest:
Delegate
s (i.e.,UserDelegate
) should extend a basicDelegate
that holds all method descriptions. This way, we could use this basicDelegate
within theCrudService
like so:Decorator
, that automatically creates all this "boilerplate code" from theextends CrudService<...>
block. This would basically just act as another wrapper.Additional context
I am using
NestJS
and need to develop a RESTful application. In this context, i cannot usebuilders
likeNexus
or whatever to bootstrapCRUD
features.Question
Do you have any idea how to properly target this issue? Keep in mind that i cannot rely on existing GraphQL packages, like
nexus
. I don't think thatgenerators
would particularly help in this case, as everything required to create aCrudService
is already there and in place - however, i cannot properly access / extend it, as there are some basic types / interfaces missing.Would it be possible to make the
Delegates
extend a basic interface that i am able to use in aCrudService
?All the best
The text was updated successfully, but these errors were encountered: