Skip to content
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

Typegoose & Mongoose on client side #33

Open
atao60 opened this issue Sep 12, 2019 · 63 comments
Open

Typegoose & Mongoose on client side #33

atao60 opened this issue Sep 12, 2019 · 63 comments
Labels
discussion A Discussion feature Adds a new Feature or Request help wanted Any help would be appreciated

Comments

@atao60
Copy link

atao60 commented Sep 12, 2019

Typegoose allows sharing of types between back-end and front-end. But it requires Mongose on client side for no reason.

So Mongoose is embedded inside the client bundle. Hence packages as Buffer. Furthermore with eg Angular 8 the need of some polyfills as:

// to avoid "ReferenceError: global is not defined" */
(window as any)['global'] = window;

// global.Buffer = global.Buffer || require('buffer').Buffer;
// @ts-ignore
window.Buffer = window.Buffer || require('buffer').Buffer;

(window as any).process = {
  env: { DEBUG: undefined }
};

Is there any plan that previous works/discussion threads on the subject would be taken in account soon?
See:

Versions

  • NodeJS: 12.3.1
  • @hasezoey/typegoose: 6.0.0-27
  • mongoose: 5.6.6
  • mongodb: 4.0.9

Code Example

import { prop, Typegoose } from 'typegoose';

export class Dummy extends Typegoose {
  _id?: string;  // surrogate key
  @prop({ unique: true, required: true }) id?: string;  // business key
  @prop() title?: string;
  @prop() description?: string;
  @prop() nature?: string;
  @prop() statut?: string;
}
@atao60 atao60 added the bug Something isn't working label Sep 12, 2019
@hasezoey
Copy link
Member

hasezoey commented Sep 12, 2019

typegoose required mongoose to work, and mongoose is a peer-dependencie, so not automaticly installed, only automaticly installed are reflect-metadata & object.fromEntries(polyfill)

and probably, no, mongoose & typegoose are not designed to work on client side / in a browser

@hasezoey hasezoey added discussion A Discussion question Qustions about how to use stuff and removed bug Something isn't working labels Sep 12, 2019
@atao60
Copy link
Author

atao60 commented Sep 12, 2019

and probably, no, mongoose & typegoose are not designed to work on client side / in a browser

Yes but I try to share "entity" classes between front-end and back-end. To keep thing simple, I want a single class per entity, no distinct interface.

typegoose required mongoose to work, and mongoose is a peer-dependencie, so not automaticly installed, only automaticly installed are reflect-metadata & object.fromEntries(polyfill)

Yes, the idea is to get rid of Typegoose or at least of Mongoose on the client side.

To be more specific, I have a Angular project with:

  • a folder for the data api (an Express server)
  • a folder for the Angular client
  • a folder 'shared' with classes such as the Dummy one of my previous comment

and a unique package.json in the project root folder.

The Angular builder will then catch the Typegoose dependencies as there are available for the data api server.

I tried to exclude Typegoose dependencies, or Typegoose itself, from the Angular build using @angular-builders/custom-webpack with typegoose and / or mongoose as "externals". It failed.

That why some work around like in HarelAshwal/typegoose-frontend will be great.

@hasezoey
Copy link
Member

i would highly recommend checking out graphql, and the typescript implementation: type-graphql, i think it would be "perfect" for angular & express

That why some work around like in HarelAshwal/typegoose-frontend will be great.

because it is "by design" not to be in browser, at least i will not support it, at least in the near future

@hasezoey hasezoey changed the title Mongoose on client side Typegoose & Mongoose on client side Sep 24, 2019
@captaincaius
Copy link
Contributor

@hasezoey if you've already finalized your decision on this, please forgive me for reviving the conversation.

But I just wanted to mention a few things:

  1. Mongoose does work on the browser. It's a little tough to set up at the moment, but it's kind of documented.
  2. Beyond the typing, most of the usefulness of doing this is being able to have your same mongoose validations on your backend also be used on the frontend! <3

I just browsed through the code today (kudos for it being so well organized and easy to follow btw!), so I hope I'm not oversimplifying. But as you mentioned, you've got mongoose as a peer dependency, so supporting this might be pretty easy - it's just a matter of bypassing mongoose.Model, mongoose.Index, and maybe a couple other little things in case it's a browser build.

Would you welcome a PR if it's not too complex?

@hasezoey
Copy link
Member

@captaincaius i will not support it, but prs are always welcome

@sebastiangug
Copy link

@captaincaius I'm interested in this too and am currently using this on the client-side. It's a complete life-saver especially when you've got 4-5 clients modifying/working with the same data. Keeping the interfaces synchronised with the mongoose models would be a nightmare.

@JakeAi
Copy link

JakeAi commented Nov 18, 2019

This is what I do. For any Client side related things I use the ICart interface (unless i make a new class on the client side, then I have that class implement ICart). The cart model implements the interface. Then on the server side (kind of hacky) you can do

Server

let cart = await CartModel.findById(dspofid90sf0);
let user = Cart.user as DocumentType<User>
user.username = "Updated and no TS errors"

Client

let cart: Array<ICart> = await http.get("/carts").toPromise();
let user = Cart[0].user as IUser
user.username = "Updated and no TS errors"
export interface ICart extends IModel {
  name: string;
  description: string;
  items: BehaviorSubject<IProduct[]> | IProduct[] | string;
  cartType: string;
  user: IUser | any // this is a ref<User> but breaks on client side, so on server side you just do Cart.User as  DocumentType<User>
}



@modelOptions(testSiteMongoDb.options)
export class Cart implements ICart {
  @prop() public name: string;
  @prop() public description: string;
  @prop() public items: string;
  @prop({ enum: CartType }) public cartType: string;
  @prop({ ref: User, required: true }) public user: Ref<User>;

  public setItems(items: any): void {
    this.items = JSON.stringify(items);
  }

}

export let CartModel = getModelForClass(Cart);

@sarangjo
Copy link

I'm running into a similar situation, where I want to have the Typescript types that come with the classes available on the client side, but the model creation is restricted to the server side. As per the Mongoose documentation, the client-side library only supports creating schemas and validating documents, which seems like a reasonable feature to support for Typegoose.

I would suggest having something similar to Mongoose, where the package is split into what can in fact be included on the client side, i.e. classes and schemas (which prepares the decorator keys, etc.) and what cannot, i.e. building the model and other functionality. On the browser, we can directly include this as follows, similar to the Mongoose pattern.

require('@typegoose/typegoose/browser');

I don't know if I'll have the mileage to actually make a PR for this approach, but it would likely be a significant refactor to modularize the code as I described above.

@hasezoey
Copy link
Member

I would suggest having something similar to Mongoose, where the package is split into what can in fact be included on the client side, i.e. classes and schemas

strictly speaking, typegoose dosnt do anything that couldnt be done in the browser, the problem is just the interop with the other mongoose package [types, etc] (this means the package wouldnt need to be split)

@sebastiangug
Copy link

sebastiangug commented Nov 27, 2019

@sarangjo @atao60

I just realized I didn't come back here to post my solution.

Using typegoose classes in the browser will cause no issues as long as you use them as a dev dependency.

From your separate module, you must export type such as:

export type IUser = UserSchema;

And then always use the types, and never the classes. The moment you import 1 single class, doesn't matter if it's even decorated or not, you'll be hit with the buffer issues and so on, msot of them caused by trying to include 'mongodb' (the npm module) code in the borwser.

However, types don't exist at runtime so you'll have no issues as long as you declare the types in your separate module.

@hasezoey
Copy link
Member

hasezoey commented Dec 9, 2019

im interested in doing this, but because i dont use it i cant test for it, could someone make a pr with an extra test directory with web(/client) tests? then i could work on "fixing" them

@hasezoey hasezoey added feature Adds a new Feature or Request help wanted Any help would be appreciated and removed question Qustions about how to use stuff labels Dec 9, 2019
@sarangjo
Copy link

@michaelbats That solution worked perfectly, thank you!

@fromi
Copy link

fromi commented Dec 16, 2019

I made a workaround for my project:
File '/config/typegoose-browser-shim.js' directly inspired from type-graphql/dist/browser-shim.js:

'use strict'

Object.defineProperty(exports, '__esModule', {value: true})
exports.dummyValue = ''

function dummyFn() { }

exports.dummyFn = dummyFn

function dummyDecorator() {
  return dummyFn
}

exports.dummyDecorator = dummyDecorator
exports.prop = dummyDecorator
exports.arrayProp = dummyDecorator
exports.getModelForClass = dummyFn

Then my webpack configuration file contains this:

webpack: (config) => {
  config.plugins.push(new NormalModuleReplacementPlugin(/type-graphql$|@typegoose\/typegoose$/, resource => {
    resource.request = resource.request.replace(/type-graphql/, 'type-graphql/dist/browser-shim.js')
    resource.request = resource.request.replace(/@typegoose\/typegoose/, '@typegoose/../../config/typegoose-browser-shim.js')
  }))
  return config
}

I basically combined type-graphql documentation for browser usage with what I need to achieve the same result for Typegoose.

Of course, typegoose-browser-shim.js must export dummy decorator and dummy function for everything originally imported from Typegoose. My example does not contains much for now (replaces @prop, @arrayProp and getModelForClass )

Even though I do not have the time to work on a complete solution right now, type-graphql browser-shim file can probably be a good start to implement this feature.

@hasezoey
Copy link
Member

@fromi, thanks for pointing this out, i might include something like this in a future version, i will need to read up on this

@hasezoey hasezoey added this to the 7.0 milestone Feb 23, 2020
@hasezoey hasezoey mentioned this issue Feb 23, 2020
12 tasks
@anbraten
Copy link

anbraten commented Mar 30, 2020

We are using typegoose in a common library which is used in the frontend and backend at the same time. Our goal is to define model-classes at a single point and use them without any fancy validation hooks etc. in the frontend.

I have currently added a wrapper module to support this:

type decoratorFn = (body?: unknown) => (target: unknown, key: string, descriptor?: PropertyDescriptor) => void;

type tg = {
  prop: decoratorFn;
  mapProp: decoratorFn;
  arrayProp: decoratorFn;
  modelOptions: decoratorFn;
  pre: decoratorFn;
  post: decoratorFn;
  index: decoratorFn;
  plugin: decoratorFn;
  [key: string]: unknown;
};

let typegoose: tg;

// if executed in browser => use dummy decorators
if (typeof process === 'undefined' || typeof window === 'object') {
  // dummy decorator
  // eslint-disable-next-line @typescript-eslint/no-empty-function
  const dummyDecorator = () => (): void => {};

  typegoose = {
    prop: dummyDecorator,
    mapProp: dummyDecorator,
    arrayProp: dummyDecorator,
    modelOptions: dummyDecorator,
    pre: dummyDecorator,
    post: dummyDecorator,
    index: dummyDecorator,
    plugin: dummyDecorator,
  };
} else {
  // eslint-disable-next-line @typescript-eslint/no-var-requires
  typegoose = require('@typegoose/typegoose');
}

export const prop = typegoose.prop;
export const mapProp = typegoose.mapProp;
export const arrayProp = typegoose.arrayProp;
export const modelOptions = typegoose.modelOptions;
export const pre = typegoose.pre;
export const post = typegoose.post;
export const index = typegoose.index;
export const plugin = typegoose.plugin;

A sample class inside the common lib looks like this:

import { prop } from './typegooseDecorators';

export class User {
  @prop({ required: true })
  id!: string | undefined;

  @prop({ required: true })
  firstName!: string;

  @prop({ required: true })
  lastName!: string;

  constructor(firstName: string, lastName: string) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
}

Maybe adding some similar code to this project checking for browser usage with simple dummy decorators would be enough for now.

@hasezoey
Copy link
Member

hasezoey commented Mar 30, 2020

@anbraten thanks for trying to help, but the real problem is to "let the imports stay how they are at the moment" and to still satisfy the types (import structure), while also "removing" usage of node-only functions/imports and to write some tests for this
-> file structure is "import first (& globals first)" (and to only use ECMA import style (no require))

PS: could you give me the name of the problem that uses typegoose for frontend and backend to know how it is used?

@hasezoey
Copy link
Member

hasezoey commented Apr 22, 2020

after thinking more in-depth about this, i couldnt find an solution for importing mongoose while also not importing it when in an browser without using an function call to get an mongoose reference
first i thought that importing mongoose and exporting it again from one file (control file) would solve this, but when putting an import or export in an if block, it would break all types and typescript dosnt allow it (and using require is not good, because of the types)
-> so i need to remove it from the 7.0 roadmap, PullRequests are welcome

PS: a solution like @anbraten used is a no-go for this project, because this would mean breaking types / duplicating much code

@hasezoey hasezoey removed this from the 7.0 milestone Apr 22, 2020
@nhhockeyplayer
Copy link

nhhockeyplayer commented Dec 20, 2021

well yeah thats what I have come to realize

POJOs are not supported though the documentation is claiming that and im not trying to start anything here about that. They should discriminate between entity vs pojo which IS the line in the sand

So I went ahead and started my own POJO hierarchy likely an abstract one to inherit off of with as much as I can model atop and two derivatives entity and pojo the former for backend and latter for front end

and the cutoff will happen at build/run time
but it would be nice to see the ecosystem up to par
the nest guys are not obligated to accomodate browser or app frameworks though it would be generous of them and I dont plan on waiting. So my ticket there (nestjs/nest#8775) is locked rather hastily and they want quiet but no need nest kicks ass already and they can and will take the back end natural model space because its perfect fit for natural world with real mini microservices back end... non-monolith architecture and if nest does permit my codebase to fly out of the gate with angular entities that is what Im trying to bring to market are fullstack micro backend micro frontends . So if they get ESM support asap its in their best interest. NestJS is the best nothing will compete and when you can write a backend like angular... well its the first back end framework to model that and all pure object oriented. Nest guys are you listening? Lets get ESM and your stuff will take the world no looking back nestJS already nailed that down and cannot be caught and will fit into AWS if not REPLACE aws.

I have yet to complete my trek here with my codebase

would something like this play a part in the fold?
https://github.com/AutoMapper/AutoMapper

@nhhockeyplayer

This comment has been minimized.

@nhhockeyplayer
Copy link

nhhockeyplayer commented Dec 20, 2021

still not out of the woods yet
reconfiguring model hierarchy (large codebase) for entity, dto, pojo and separating them appropriately for the build and runtime front/back
at this time my nest project wont build from the @prop descriptor decorator on nest js project so Im still hammering away

I wont have feedback soon
being pre-empted again

but will see this thru when my models are cast and booting for either a typegoose workaround or what I might be seeing as something else preventing me from building nest project

thanks for your patience

@nhhockeyplayer
Copy link

nhhockeyplayer commented Dec 20, 2021

feedback, still trying to spot the lapse here some things make sense and other things do not

something changed from typegoose 5 to typegoose 6+

in typegoose 5 I extended from typegoose
there was no mandate for any models to extend from mongoose.Document

and I flew with typegoose 5 and released a public koa2 node.js server running today still

so now Im being blocked with typegoose 6+

and the mandate is the models have to extend from mongoose.Document

why has this changed if I was able to load models anywhere on anything

Wouldn't this limit the agility? Did someone impose this just for good housekeeping?

Its not normal I am impeded but this one is taking me for a ride and that alone cites something went arye somewhere that is serious I shouldnt be impeded like this
and the theories I am trying to make sense of some are panning out some not

I dont know the internals well enough of the change from typegoose 5 to typegoose 6+ to say

does anyone know the semantics behind this?

should it be rolled back? certainly the 2019 web chatter on this caused a flurry on same thing

if typegoose models can load independent of mongoose that would be ideal no?

can we make it like this?

Im not going to get clarity on my space for a bit still and Im running into road blocks modeling a dual hierarchy that NEEDS to at one point extend from mongoose.Document I can tag it at the end of the extends or all classes will be mongoose.Document and Im not seeing a front end delegate yet besides a property named DATA that gets self populated on the fly (gamma pattern delegate) which I find atrocious at a high level hierarchy Im trying to run with. Though Im still going to keep trying

it would help to know what the change occurred from typegoose 5 to 6+ and if it was a legitimate and valid change or if it was a mistake

any light is appreciated on the 5 to 6+ change for mongoose.Document and advocating the community operate what they deem a POJO (which it isnt if it cant run isomorphically on front end and backend source code) by changing their models to what they call a POJO by NOT extending from typegoose for models like we did in version 5

thanks

@nhhockeyplayer
Copy link

https://typegoose.github.io/typegoose/docs/guides/migration/migrate-7/

Typegoose class got removed​
In 6.0.0 it was announced that the Typegoose class was useless and will be removed in a future version. Now, in 7.0.0, it was completely removed.

@hasezoey
Copy link
Member

and the mandate is the models have to extend from mongoose.Document

classes were never needed to extend mongoose.Document, but was actually not recommended to be done (and i think in higher version it actually gives warnings or is incompatible with types)

why has this changed if I was able to load models anywhere on anything
Wouldn't this limit the agility? Did someone impose this just for good housekeeping?

typegoose is intended to be used with full typescript, and only with typescript that is why there a stricter types than working just in js or having normal js interop

if typegoose models can load independent of mongoose that would be ideal no?

no, typegoose just translates classes into schemas (and most commonly also directly into models)

it would help to know what the change occurred from typegoose 5 to 6+ and if it was a legitimate and valid change or if it was a mistake

there is a migration guide and also a Changelog


please keep this discussion in its own thread, because this does not seem to be "just" about "on client side"

@nhhockeyplayer
Copy link

nhhockeyplayer commented Dec 20, 2021

this does not feel right

5.0 flew fine

and it seems for the sake of typings this was imposed

Im not getting the rationale if we cannot load models anymore this appears to me to be more like a regression
and a show stopper

shoot me some flames I dont mind
but when something goes backwards mechanically that alone does not sit right regardless of what mongoose is doing when it was fine before

here is my 5.0 model

import * as mongoose from 'mongoose'
import {pre, prop, Typegoose} from 'typegoose'

export class HitCount extends Typegoose {

    @prop()
    _id?: mongoose.Types.ObjectId

    @prop({required: true})
    hits: number

    @prop()
    owner: mongoose.Types.ObjectId

    @prop()
    createdAt?: Date = new Date()
    @prop()
    updatedAt?: Date = new Date()
}

export const model = new HitCount().getModelForClass(HitCount, {schemaOptions: {collection: 'hitcount'}})

I had implemented this class below interface IHitCountModel extends IHitcount, mongoose.Document { anyway but realized it was not even used in my source back in 2017 and it sits unused by any other classes or objects so it's vestigial and the POJO I modeled for 5.0 ran like you see fine as-is front end and backend just by referring to the interface IHitCount

import * as mongoose from 'mongoose';
import IHitcount from '../../../ext/isomorphic/IHitcount';

interface IHitCountModel extends IHitcount, mongoose.Document {
  _id: any;
}
export default IHitCountModel;

and this is all I needed on the client side to peel models off the http bridge with my service layer

import { Types } from 'mongoose'

interface IHitcount {
    _id?: Types.ObjectId;
    hits: number;
    owner: Types.ObjectId;
}

export default IHitcount
import { Injectable } from '@angular/core'
import { BaseService } from '@app/core/service/base.service'
import { Observable, throwError as observableThrowError } from 'rxjs'
import IHitcount from '../../ext/isomorphic/IHitcount'
import { HttpClient } from '@angular/common/http'
import { LocalStorageService } from '@app/core/local-storage/local-storage.service'

import { catchError } from 'rxjs/operators'

import { ExtensionStateContainer, HITCOUNT_PROPERTY_KEY } from '../biz/extension/ngrx/extension-state'
import { select, Store } from '@ngrx/store'

@Injectable()
export class HitcountService extends BaseService {

    find() {
        return this.http
            .get<IHitcount[]>(this.apiUrl)
            .pipe(
                catchError((error: any) => {
                    console.log(error.status + '\n' + error.error + '\n' + error.message + '\n' + error.url)
                    return observableThrowError(error.message)
                })
            )
    }

thats it

@nhhockeyplayer
Copy link

so its the migration from 6 to 7
and I still dont understand the rationale to limiting models forcing extending from mongoose.Document
its taking out abstraction on the front ends and forcing all models to be backend only

did anyone see this impact during the switch?

@nhhockeyplayer
Copy link

SO How can anyone agree with the regression or justify it

@hasezoey
Copy link
Member

hasezoey commented Dec 20, 2021

and this is all I needed on the client side to peel models off the http bridge with my service layer

if its really just types, then maybe consider using import type { YourClass } from "./Somewhere", if possible

5.0 flew fine

because comparatively, that version was a mess

and I still dont understand the rationale to limiting models forcing extending from mongoose.Document

like i said earlier, it is recommended to not extend mongoose.Document, because this is not what the (runtime and compiletime) types actually are

did anyone see this impact during the switch?

from what i can tell not really (see initial version of this issue's author), also there are were and still are no tests for client end (read my earlier comments about it)

its taking out abstraction on the front ends and forcing all models to be backend only

from what i can tell, typegoose 5 to 6 did not take out any functionality, only defined it more clearly


also, like i said earlier, it is not recommended (or supported) to use typegoose (or even just mongoose) on the front-end / client side / in the browser

@nhhockeyplayer
Copy link

nhhockeyplayer commented Dec 20, 2021

Im not using typegoose on the front end Im justy trying to adhere to an interface that a typegoose model implements

my model is displayed above
see my 5.0 model

but now
7+ models are required to extend from mongoose.Document and thats the regression thats been caused

I cant use the interface anymore it has to extend from mongoose.Document

this is what was imposed taking out my codebase

@nhhockeyplayer
Copy link

thank you for feedback

I will keep working this

and hopefully something will break if Im overseeing something

but normally I am not impeded like this and I have the public server and code to prove my assertions

@hasezoey
Copy link
Member

7+ models are required to extend from mongoose.Document and thats the regression thats been caused

i still dont quite understand what you want to say here.
typegoose classes are not meant to be extended from mongoose.Document, and i think v7 was the first version where typescript would give you some kind of error when trying to do that

also, even if you would extend from mongoose.Document, you would live in the assumption of wrong runtime types where that value would never actually be a instance of a Document

@nhhockeyplayer
Copy link

nhhockeyplayer commented Dec 20, 2021

at least I offered you my position coming back to typegoose since 2017
I would rather stick with it instead of typeorm

and now that I have conveyed

it seems extending from typegoose class was sufficient to build and run in 5.0

can the typegoose team at least take this into account and ponder about this

and maybe a small refactoring can be done to undo this regression?

I hope that time will allow that and something can break from this so typegoose 5.0 models can be used still

@nhhockeyplayer
Copy link

nhhockeyplayer commented Dec 20, 2021

STK913 posted a workaround
for he is seeing the same thing
im not missing anything

this is real and happened
and the 2019 chatter speaks alot on this when people were trying to run with typegoose then

I hope some time is taken to ponder in this

and maybe a refactor will come about

thanks

@nhhockeyplayer
Copy link

nhhockeyplayer commented Dec 20, 2021

wow I think I may have just solved it

by commenting

in my original 5.0 code

I use
Types.ObjectId;

one gentleman on this thread stated mongoose has typdefs now @types/mongoose
which I overlooked

I look at my current codebase and its all mongoose.types.ObjectId

so I just imported (like my 5.0 code) like this
import { Types } from 'mongoose'
instead of this
import { mongoose } from '@typegoose/typegoose'

and my red compilation errors go away after I change
mongoose.types.ObjectId;
to
id?: Types.ObjectId

so I will see this thru my current codebase now and get my build out of the way

results pending on runtime though im not sure of this exception will continue but I feel confident
typedefs are for that purpose to operate framework objects everywhere

results pending

@nhhockeyplayer

This comment has been minimized.

@nhhockeyplayer

This comment has been minimized.

@nhhockeyplayer

This comment has been minimized.

@nhhockeyplayer

This comment has been minimized.

@hasezoey
Copy link
Member

hasezoey commented Jan 3, 2022

@nhhockeyplayer please keep it on-topic (which in this issue is "Typegoose & Mongoose on client side")

@dan-cooke
Copy link

Just want to point out I was suprised that the remix compiler is smart enough to strip out these decorators for client side code, allowing us to share our typegoose classes with the browser.

May be worth looking into what they do with esbuild

@smolinari
Copy link
Contributor

I still don't see the value. 🤷 As I see it, the client should be ingesting DTOs, which should be different than the DAOs (Typegoose classes). The DTOs might share similar names and shapes, but overall, they should be 100% separate so that when data access APIs change, your client APIs won't have to, i.e. causing painful breaking changes.

My 2 cents.

Scott

@dan-cooke
Copy link

dan-cooke commented Mar 5, 2022

@smolinari the value in my experience is for an app where the API only has JS /TS consumers. Especially if working in a monorepo

This way. When you carry out a breaking change to your API you can simply update all of client applications with a global rename.

That is why this is very powerful in my opinion.

Could you give me an example of some "differences" between the DTO and DAO. I really am struggling to think of any.

@smolinari
Copy link
Contributor

@dan-cooke - Examples of differences.

Not sending out passwords or requests are specialized to not entail personal data.
A requested resource isn't the same or an aggregate of stored data.
In terms of an API, the controllers return DTOs, not DAOs.

Off the top of my head.

Scott

@dan-cooke
Copy link

@smolinari okay yeah fair enough. And in that case sharing a data model between the frontend and backend is probably not wise (although could still be achieved by making the personal fields nullable)

But for many applications there is a consistent contract between the API and Frontend.

I have used several tools that generate the frontend types from the backend schema.

Sharing those types directly is just cutting out the middleman.

I think the biggest problem we are trying to solve here is one of maintainability.

Maintaining 2 type defs for every model (1 client and 1 API) takes twice as long as if we could just share the 1 type between both.

I get that not every application can safely do this, but many can. And I think its worth pursuing the changes required.

@smolinari
Copy link
Contributor

smolinari commented Mar 5, 2022

@dan-cooke - I've gone a different route with GraphQL and graphql-code-generator. I have a CLI tool (still in POC) that uses gcg to read the introspection API of my GraphQL API and builds out both the "useX" classes for querying and mutating and also the TypeScript types. It's pretty awesome.

https://www.graphql-code-generator.com/

Scott

@dan-cooke
Copy link

dan-cooke commented Mar 5, 2022 via email

@johnobrien8642
Copy link

(typegoose & mongoose are meant for database interaction, not making DTO's / POJO's)

Do you mean typegoose isn't? I'm using mongoose to create a single source config for forms, i.e. I fetch schema.paths for models to auto-create forms, meaning I'm taking advantage of mongoose's ability to give me POJO's of their schemas. Off-topic a little, but in relation to the future of this project, I was going to use it, but because you're deviating from mongoose setup, now I won't be able to use it. Just feedback.

@HarelAshwal
Copy link

If you're still looking for a workaround to make your model work both on the client and the server, here's a trick:

Create a Prop Wrapper Class

Create a file called propWrapper.ts with the following content:

let prop: any;

function isNode(): boolean {
    return typeof global !== 'undefined' && typeof process !== 'undefined' && typeof require !== 'undefined';
}

if (isNode()) {
    const _require = eval('require');
    const { prop: typegooseProp } = _require('@typegoose/typegoose');
    prop = typegooseProp;
} else {
    // Dummy implementation of prop
    prop = () => (target: any, key: string) => {};
}

export { prop };

Import the Prop Wrapper

Instead of importing prop directly from @typegoose/typegoose, import it from your propWrapper:

import { prop } from './propWrapper';

With this approach, you can use the same model code in both the client and the server!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
discussion A Discussion feature Adds a new Feature or Request help wanted Any help would be appreciated
Projects
None yet
Development

No branches or pull requests