An ORM for javascript and typescript
Inside your project, run one of the following commands to install Elder
- via Yarn:
yarn add elder-core
- or via npm:
npm install elder-core
While you could write all your ORM code in a single file if you wanted, we recommend breaking up your code in the following way.
We recommend creating the following directory structure when using Elder. This is in part because once a cli tool is developed, it will most likely follow the conventions laid out in this guide.
/my-project
/orm
/models
/types
config.ts
index.ts
The orm/models
directory is where you should put your model files. Models are the most important part of Elder and
each model represents a table in a database. When naming your files we recommend 1 file per database table named in
singular form, lower case and with dashes used to separate any words.
Examples
models/post.ts
models/post-comment.ts
Types are a way to tell the elder how to map you model's properties to columns in the database. Elder comes with some base types which can be used directly or as a starting point for building more complex types.
The types Elder ships with are:
string
boolean
date
number
Types are very flexible and can include validations. You are encouraged to build various types to meet your applications needs. See the documentation on types for more information.
Types should be placed in separate files inside the /types
folder. Naming should follow a singularized, lowercase,
dash separated convention.
Examples:
/types/age.ts
/types/cat-age.ts
The config file is used, among other things, to store details about the database connection.
For now we just need to create the file /orm/config.ts
and populate it with the following
information. (Changing values as appropriate).
Example
export default {
adapters: {
default: {
host: '127.0.0.1',
user: 'user',
pass: 'sshhh',
port: 5432
}
}
}
The property adapters
with a nested property default
tells Elder to use these
connection details whenever the default database adapter is used. It's possible
to define additional adapters, more about that in the adapters section.
In the index file we need to tie everything we have laid out so far together.
First we import the Elder
module from elder-core
, then our config file.
Next we import each type we have defined in our type folder (none so far, we'll get to that) and each model file we have defined in our model folder. (Coming up in the next section).
Finally, we create and export a new instance of Elder
passing it all the things we
have imported.
Example:
import Elder from 'elder-core'
import config from './config'
/*
import CatAgeType from './types/cat-age'
import DogSizeType from './types/doge-size'
*/
/*
import Cat from './models/cat'
import Dog from './models/dog'
etc
*/
export default Elder.create({
config,
types: {
/*
give any custom types we have defined and imported here:
'cat-age': CatAgeType,
'dog-size': DogSizeType
*/
},
models: {
/*
give any models we have defined and imported here:
'cat': Cat,
'dog': Dog
*/
}
})
Worth noting also is that there are several other concepts working behind the scenes.
Both adapters
and serializers
are always present but Elder will give you the
defaults if you omit them from setup. You can read more about these 2 concepts
in their respective sections of the guide.
With project structure in hand we can now start creating models to use to interface with our database.
Each model should be in it's own file in the orm/models
directory. If we were
to create a Cat
model, the file should be orm/models/cat.ts
.
Model files should export a single model and extend either the base Model
class
or another model class.
Example
import { Model } from 'elder-core'
export default Cat extends Model {}
We define mappings between properties on our model and columns in the database using
the @type
decorator. For now, we will just use the built in basic types
string
, number
, date
and boolean
as we haven't yet defined any application
specific types (which we will do in the next section)
Example
import { Model, type } from 'elder-core'
export default Cat extends Model {
@type('string') name
}
We have told Elder that our cat has a corresponding name
column in the cat
table and that it is string based (which in the case of postgres is varchar).
When defining models we saw that we need to map model properties to database table
columns and to do that we use the @type
decorator. One of the key features of
Elder is that is possible and even encouraged to develop different mappings to
suit your needs. This is great because you can easily add support for database
specific features eg. geospatial and you can add validations to ensure data
integrity.
The best way to define a type is to extend an existing type. We can choose the type the most closely resembles our needs, override whichever methods make sense and go from there.
By convention, type classes should be placed in the orm/types
folder and be named
singularly, lowercase with dashes instead of spaces.
Heres an example of creating a type that represents the age of a cat. It adds validation to make sure the value is between 0 and 30 (Or can cats get older than that??)
Example
// orm/types/cat-age.ts
import { NumberType } from 'elder-core'
export default CatAgeType extends NumberType {
validate (value: number): boolean {
return value >= 0 && value <= 30
}
}
And now back in our model file, we can reference this new type using our @type
decorator like so:
Example
// orm/models/cat.ts
export default Cat extends Model {
@type('string') name
@type('cat-age') age
}
Finally we need to go back to our orm/index.ts
file and load in our cat
model
and cat-age
type.
Example Example:
import Elder from 'elder-core'
import config from './config'
import CatAgeType from './types/cat-age'
import Cat from './models/cat'
export default Elder.create({
config,
types: {
'cat-age': CatAgeType
},
models: {
cat: Cat
}
})
We should now have an Elder ORM setup and ready for use. For the purposes of
demonstration, we can create an index.ts
file at the root of our project where
we can import our ORM and start using it.
Create index.ts
in the project root so that your project looks like the following
Example
/my-project
/orm
/models
/types
config.ts
index.ts
index.ts <-- add this file
Inside index.ts
we should now be able to import our ORM and start using it's API.
Example
// my-project/index.ts
import orm from './orm'
const Cat = orm.models.cat
/* Cat.all().then(...) */