I'm working on a full-stack TypeScript project using Next.js, and I'm trying to share TypeScript definitions between Node and React. I'm using Obje...
For further actions, you may consider blocking this person and/or reporting abuse
you can set
"strictPropertyInitialization": false,
in you tsconfig and don't have to use!
Dang, I had no idea! This is way better. Thank you for sharing, Hunter.
Hi,
I found a similar solution a few months ago (don't remember the source though). It's especially good as I don't want to use default exports.
Instead of having an identically named interface to extend your original interface, you must export the class first (which extends Model) and then the new interface which extends both your actual interface and the Model class:
(Sorry, new user on dev.to, didn't know how to present code and went with JSFiddle.)
But either way, this solution in this article or my approach, both cause problems when you want to start using
relatedQuery
.For example:
const result = await BlogPostModel.relatedQuery('images').for(1);
would return this error:Property 'for' does not exist on type 'never'.ts(2339)
Have you come across this problem and if yes, how would you solve it?
Hey Veiko, welcome to DEV!
If you're just trying to use types within the back-end Node.js app, it may be best to define your properties directly on the model. Here's a simplified example from Objection's official TypeScript example project:
This should give you types and autocomplete. Objection makes this example project hard to find because it's at the bottom of the homepage with no fanfare.
If you want to share those types with a front-end application, take a look at Hunter's example further up in the comments.
If you want to leave a comment with code on DEV, you can use three backticks (`) followed by the language, the code, then three more to close the code block. It should look like this in your comment box:
I hope this helps. Cheers!
Hi Tyler!
Thanks for the really helpful and quick response :) I really appreciate it.
So I also posted this question on Objection github and got a response that I can't do both those things I wanted to do. That indeed, for the "relatedQuery" to work, you have to have the properties defined in the Model class.
So I am currently trying to change all my backend logic accordingly, but before I get it all done and can test stuff, my first concern would be this:
How to handle the case when I have multiple interfaces for one entity? An easy example based on your BlogPost here is for read/write, let's say your full BlogPost interface would have a property called "createdDate" but you don't want that to appear in the interface which is used for creating blog posts because you don't want anybody to accidentally enter this value themselves during blog post creation.
Or another example, for user creation you want to have a password field but when you return a user object to the FE, you don't want to pass the password value back (even if it's hashed at that time).
I get that actually in JS you can add more properties to an object compared to its interface anyways and to be perfectly safe not to add any unwanted data to your DB, you should clean the object before that (or just cherry pick the fields from the input object you need) but still, when our FE devs work on their tasks, they only want to work with an interface for entity creation that has all the required fields and not wonder whether they should fill "creationDate" or similar fields on FE themselves.
So would the best solution then be to define such an interface, have it as an input to a service function and then manually create a DB Model object, map all the required fields from input to model and then insert that model object to the DB?
Btw, here's the link to the github issue, Sami Koskimäki suggested me to just create that separate interface and make the model Implement that. This does create some problems with Partial objects which didn't seem to occur with this Hunter's type approach, but then again having a separate interface seems to be a more decoupled approach.
I'll try them both and see which one works better for me.
Thanks for your help!
I hope you're able to get it sorted out! Feel free to leave a comment with which one you liked better incase anyone else who is having the same problem stumbles onto this conversation in the future.
Thanks!
I will.
But first I need to solve some issues. With this "type" solution offered here, I'm immediately running into problems when creating new objects of this new type "Dataset" (based on DatasetModel).
Although it hides most of Model's properties, it still includes
QueryBuilderType
and when instantiating a new object, this is a required field to add as well. If I don't, I get this error:Type ... is missing the following properties from type 'ModelObject<DatasetModel>': QueryBuilderType
Edit:
And when I have an example like this:
I'm running into a problem when I want to instantiate a Project object. I need the "datasets" property to be defined for the Project so I can use it on the object (with autocomplete etc), only relationMapping is not enough. But when its type is DatasetModel (which is the only way
relatedQuery
works), then I also need to explicitly set the ~30 properties that the Model class has as well. Not to mention the fact that then you need to use the DatasetModel class in your business logical (service) layer which means that the DB communication (ORM) implementational details are not decoupled from the rest of the app anymore.If I use Hunter's solution with the Type and set the "datasets" property to type "Dataset" for the Project class, then I only need to set the value to 1 extra property (QueryBuilderType, as stated above), but this again breaks the usage of
relatedQuery
and I'm back to square one.So I think now I have to start trying with the interface "implements" approach.
But it's so crazy how you can't easily define DB model entities, which have other entities as related properties, all based on interface contracts in Typescript and also use all the query functionality properly.
This is a hack, probably not recommended, but this can work:
Upside,
You can add your database columns as part of the model and your IDE should pick them up
Downside
Because of the
[k: string]: any
property, Typescript will not warn if an undefined property was accessed on instances of BlogPost.I.e.
The code above will fly with Typescript, instead of it previously highlighting
post.undefined_property
...I am finding that combining this with an interface (shared with the UI) helps to keep them in sync. The interface will cause errors if your model is missing any fields
Very nice explanation. Looking 👀 forward to hearing more in the future. Welcome!
I like your solution more than Hunter's because I am trying to follow Clean Architecture, and want my Objection models to reference domain entities, rather than the other direction. Thanks!
Are you talking about Uncle Bob's book by the same name? I haven't read that one, do you recommend it?
thanks!