Giter Site home page Giter Site logo

sakuraapi / core Goto Github PK

View Code? Open in Web Editor NEW
36.0 36.0 4.0 1.75 MB

MongoDB and TypeScript MEAN Stack Framework for NodeJS

Home Page: https://blog.sakuraapi.com

License: BSD 3-Clause "New" or "Revised" License

TypeScript 99.39% Shell 0.61%
javascript mongodb nodejs nodejs-api nodejs-framework nodejs-mongodb typescript

core's People

Contributors

candleshine avatar carsonf avatar etsuo avatar stevenschobert avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

core's Issues

`@Model` DB Related Directives

There are a number of directives from #13 that cannot be implemented until #5 is implemented. See the epic:db label.

  • Implement basics of @Model
    • Implement dbConfig for ModelOption
    • inject create (instance)
    • inject save (instance)
    • inject delete (instance)
    • inject get (static)
    • inject getById (static)
    • inject delete (static)
    • make sure CRUD methods allow for mongodb options
    • make sure get methods allow for mongodb field projection
    • inject fromDb static method to instantiate a Model Object from a DB result
    • inject fromDbArray static method to instantiate an array of Model Objects from DB results
    • inject toDb static method
  • @Required -- see #36
  • @Db
    • fromDb integration
    • update CRUD to respect @db
      • crudCreate
      • crudSave
      • crudDelete
      • crudGetStatic
      • crudGetByIdStatic
    • implement private field in @db to prevent @JSON from marshalling that field
    • implement DbOptions or string parameter (the latter mapping to DbOptions.field)
  • @Validate moved to #38
  • Make sure you have all the debugs in place

Establish standards for HTTP Response Codes

In his book, "REST API Design Rulebook", Mark Masse proposes the following:

  • Rule: 200 (“OK”) should be used to indicate nonspecific success
  • Rule: 200 (“OK”) must not be used to communicate errors in the response body
  • Rule: 201 (“Created”) must be used to indicate successful resource creation
  • Rule: 202 (“Accepted”) must be used to indicate successful start of an asynchronous action
  • Rule: 204 (“No Content”) should be used when the response body is intentionally empty
  • Rule: 301 (“Moved Permanently”) should be used to relocate resources
  • Rule: 302 (“Found”) should not be used
  • Rule: 303 (“See Other”) should be used to refer the client to a different URI
  • Rule: 304 (“Not Modified”) should be used to preserve bandwidth
  • Rule: 307 (“Temporary Redirect”) should be used to tell clients to resubmit the request to another URI
  • Rule: 400 (“Bad Request”) may be used to indicate nonspecific failure
  • Rule: 401 (“Unauthorized”) must be used when there is a problem with the client’s credentials
  • Rule: 403 (“Forbidden”) should be used to forbid access regardless of authorization state
  • Rule: 404 (“Not Found”) must be used when a client’s URI cannot be mapped to a resource
  • Rule: 405 (“Method Not Allowed”) must be used when the HTTP method is not supported
  • Rule: 406 (“Not Acceptable”) must be used when the requested media type cannot be served
  • Rule: 409 (“Conflict”) should be used to indicate a violation of resource state
  • Rule: 412 (“Precondition Failed”) should be used to support conditional operations
  • Rule: 415 (“Unsupported Media Type”) must be used when the media type of a request’s payload cannot be processed
  • Rule: 500 (“Internal Server Error”) should be used to indicate API malfunction

This seems like a good place to start?

`@Required`

There needs to be some kind of decorator for required fields... the behavior needs to be thought out. It's tied to at least fromJson... maybe more... see #21.

Add before and after behavior for a route

@Routable({
    model:  User,
    baseUrl: 'user'
    beforeAll: [],
    afterAll: []
})
class UserApi {

    sanitizer(req, res) { }

    @Route({
        method: 'get',
        path: ':id'
        before: [ this.sanitizer, this.handleGetBefore ]
        after: [ this.handleGetAfter1, this.handleGetAfter2 ]
    })
    handleGet(req, res) {
    }
   
    handleGetBefore(req, res) {}
    handleGetAfter1(req, res) {}
    handleGetAfter2(req, res) {}

    @Route({
        before: this.sanitizer
        after: [this.getAllRouteHandler, this.handleGetAllAfter]
    })
    handleGetAll(req, res) {
        // this method will get executed first, then it will run the default getAllRouteHandler
        // then this.handleGetAllAfter
    }
   
    handleGetAllAfter(req, res) {}
}

`@Model` ids option to let SakuraApi know which properties to autoconvert to ObjectId

Is it sufficient to use TypeScript to type a property as ObjectID or do we need to have an explicit option... something:

@Model({
    //...
})
export class SomeModelObject {

    @Db({field: 'oid', model: ObjectID}) 
    @Json()
    ownerId: ObjectID;
}

Because of work done on #59, @Db now supports a model option. This can be augmented such that any property with an @Db({model:...}) decorator could be used for this purpose.

  • fromDb
  • toDb
  • fromJson (uses @Db({model: ...})
  • toJson

See also #72

Update documentation

To Do:

  • Update documentation
  • Migrate Model's getCollection out of the constructor function
  • Migrate Model's toDb out of the constructor function

Integrate `@Model` and `@Routable` for default API

@Routable({
    baseUrl: 'use',
    model: User,
    suppressApi: ['delete']
})
class UserApi {
    @Route({
        method: 'get',
        model: UserVote
    })
    getSomethingElse() {
    }
}

The idea here is that @Routable will take a optional model property which can be any Class that's decorated with @Model. If a model is provided, by default, the @Routable class will support:

  • GET model
  • GET model/{id}
  • PUT model (with the change set being in the body and the change set requiring an id property)
  • PUT model
  • DELETE model

Since the @Routable optional suppressApi property has the string delete provided in its array, a route for DELETE model/ will not be created.

Alternatively, exposeApi also takes an array of strings, but it defaults to all APIs being suppressed except those explicitly exposed.

APIs include:

  • - get [GET /{:id}], returns an object
  • - get [GET /{:id}?filter={}], returns an object
  • - getAll [GET], return array of objects
    • basic functionality
    • ?where={}
    • ?fields={} // exclude / include certain fields
      • - fromDb strict mode support for projection
      • - toJson support for projection
    • ?skip=# // skip x results
    • ?limit=# // limit to x results
    • prevent NoSQL injection (moved to #65)
  • - put [PUT /{:id}], takes a change set with id and updates a document
  • - post [POST], takes an object and creates the document
  • - delete [DELETE /{:id}, deletes a document
  • - mapJsonToDb static function on model

Implement initial scaffolding

Implement the initial project structure, testing, etc. I.e., get to the point where you have a basic project foundation on which to build.

@Model

Todo:

  • Implement basics of @Model
    • [ ] Implement dbConfig for ModelOption (moved to #21)
    • Implement suppressInjection for ModelOption
    • Default CRUD function injection (stubs, since #5 isn't implement)... also, what about #6?
    • inject toJson instance method;
    • inject toJsonString instance method;
    • inject fromJson static method;
    • inject fromJsonArray static method;
    • [ ] inject fromDb static method; (moved to #21)
  • @Json
  • @Default
  • @Required (moved to #21)
  • @Db (moved to #21)
  • @Private
  • @Validate (moved to #21)
  • Separate @Route into a separate file from @Routable
  • Separate tests from @Route from @Routable tests

Proposal

A set of route handlers are setup with an @Routable class:

@Routable()
class SomeSetOfRouteHandlers {

  @Route({
    path: '/',
    method: 'get'
  })
  someGetHandler(req, res) {
  
  }

  @Route({
    path: '/',
    method: 'post'
  })
  somePostHandler(req, res) {
  
  }
}

This class is instantiated by the @Routable decorator and added to the Express router through a call to SakuraApi.instance.route(this);.

Because there's a single instance of this class responsible for route handlers, it's not an appropriate place to represent a model (i.e., there's only one of these @Routable objects handling routes, but each request will be handling different objects that are relevant to various models).

Possible Solution?:

@Model({
  // these get injected as a dbConfig property that can be used when
  // manually writing to the DB
  dbConfig: {
    db: 'someDatabase',
    collection: 'someCollection'
  },
  // if not defined, perhaps look at the configs for a dbModelBindings.json file
  // (db.ModelBindings.dev.json, etc)
  suppressInjection: ['create']
})
class User {

  id: ObjectId; // object Id will have timestamp capabilities 
                       // & uuid generation on instantiation
                       // the id property will automatically be recognized as
                       // being the _id for MongoDB

  // perhaps these decorators create getters and setters that manipulate an
  // injected _propertyName variable and decorate the getter with various
  // functions that implement the Decorator behaviors?

  @Required 
  @json('fname') @db('fn')
  firstName: string;

  @Required
  @json({'name: "fname"', excludeNull: true}) 
  @db({field: 'fn', excludeNull: true})
  lastName: string;

  @Required @Validate(emailValidator)
  @db('em')
  email: string;

  @Private        // i.e., filter this before sending to client
  ssn: string;

  constructor(json: any) {
    json = json || {}; // perhaps this can be done in the @Model logic

    this.firstName = json.firstName || '';
    this.lastName = json.lastName || '';
    this.email = json.email || '';
  }

  save() : Promise<null> {
    // persist to MongoDB
    super.save(); 
    // if nothing's added, you don't have to override the save()
    // method.

    // There will need to be some kind of DataSource definition that binds an
    // @Model to a specific DB / Collection... perhaps that can be done in 
    // the @Model({})
  }

  static getById(id) : Promise<User> {
    // retrieve from MongoDB
  }

  static get(filter:any) : Promise<User[]> {
    // get list from MongoDB
  }

  deleteById(id) : Promise<null> {
    // delete from MongoDB
  }

  emailValidator(email:string) {
    return (...meets some proper condition...) ? true : false;
  }
}

Then, from within the @Routable class @route method:

  @Route({
    path: '/',
    method: 'post',
    model: User
  })
  somePostHandler(req, res) {
    req.model.save();
    // where .model was patched onto req automatically.
  }

  @Route({
    path: '/',
    method: 'get'
  })
  somePostHandler(req, res) {
    res.send(200).json(User.get(req.query.id));
    // this requires a lot more thought... what about error handling, etc?
  }

Bootstrap a @Routable class

Right now, you can't do this:

// ...

@Routable()
class SomeClass {
     constructor() {
          SakuraApi.instance.route(this);
     }
}

The reason why is the sakuraApiClassRoutes array hasn't been added to the object yet. See: routable.ts:Routable(...).

Perhaps this could be refactored so the above scenario can become the pattern for bootstrapping a routable class.

Alternatively, a decorator could do the job:

// ...

@Routable()
@AddToRouter
class SomeClass {
     constructor() {
          SakuraApi.instance.route(this);
     }
}

Another possibility is maybe SakuraApi.instance.route(...) can be called from within Routable(...)??

`@Validate` decorator

Functionality TBD, but basically make sure a property is in a valid state before persisting it to the DB.

Database ODM

There needs to be a story about how an @Routable() class's properties can also be persisted to MongoDB.

@Routable({
     baseUrl: 'user'
})
class User {
     fisrtName: string;
     lastName: string;

     @Route({
          path: '/:id',
          method: 'get'
     })
     getUserById(...) { }

     @Route({
          method: 'post'
      })
     saveUser(...) {}
}

How does that thing get persisted?

Add Indexing to Model

@model({
     dbConfig: {
          db: 'userdb',
          collection: 'users',
          indexes: [
               {
                    spec: 'email',
                    options: {
                         unique: true
                    }
               }
          ]
     }
})
SomeClass {
}

Integrate an API explorer that documents the system's API

Some thoughts

  • This should ideally comply with the OpenAPI specification.
  • It should be configurable / excludable (ideally a separate npm module that gets added if wanted)
  • It should be controllable with the ACL system (#7)

Proposed Example

export class GetSomethingBody {
    @Desc('Description name field')
    @Required() @Default('')
    name: string;
}

@Model()
@Desc('SomeModel is a Model that changes the world')
export class SomeModel {
    @Desc('Description of name field')
    @Db() @Json()
    name: string;
}

@Routable({
    baseUrl: 'some-api-class'
})
export class SomeApiClass {

    @Route({
        authenticator: AuthAudience,
        method: 'get',
        path: 'get-something',
        expects: GetSomethingBody,
        returns: [SomeModel, SomeOtherModel]
    })
    @Desc('get-something is a handler that handles the things that need to be handled')
    getHandler(req: Request, res: Response, next: NextFunction) {
    }

}

Decorator Routing

Initial pass at routing based on decorators.

As a developer, I'd like to be able to add routes declaratively via typescript decorators to simplify the wiring up of APIs.

For example:

@routable({
  baseUrl: 'test'
})
class Test {
  constructor() {
  }

  @route({
    path: '/',
    method: 'get'
  })
  someMethod(req: express.Request, res: express.Response) {
    res.status(200).send({});
  }

  @route({
    path: 'someOtherMethod/',
    method: 'post'
  })
  someOtherMethod(req: express.Request, res: express.Response) {
    res.status(200).send({});
  }
}

This can then be added to the router with sakuraApi.route(Test).

Standardize Error Handling

 @Route({
    path: '/',
    method: 'get'
  })
  somePostHandler(req, res) {
    res.Error(new Error('something's wrong'), {
        status: 400
    });

    // or 

    res.Error(new Error('something's wrong'), 400);    
  }

Integrate MongoDB

SakuraApi needs to have a property (e.g., dbConnections) from which @Model (etc) will be able to grab a reference to a db connection.

This ticket should also handle how the configuration for these db connections is defined (using the SakuraApiConfig system).

What should be the proper behavior of @Route({path:null/undefined}) & @Route({path:''})?

I changed the @route path behavior to default an empty path value to the method's name. The implementation needs to be checked to make sure that '/' is still allowed, otherwise you won't be able to route to '/', which is desirable behavior in most circumstances.

I guess there's a question here about what the right behavior is... should @route({}) default path to the method name (e.g., someBaseURI/someMethodName) or should it default the route to the baseURI (e.g., someBaseURI/)?

Possibilities:

Option 1:
path:'' = baseURI/
path:null/undefined (or left out) = baseURI/methodName

Option 2:
path is always explicitly required or an exception is thrown

Option 3:
path:'' or path:null/undefined always resolve to baseURI/

Option 4: ???

Integrate Travi CI

When developers commit changes, Travis should run unit tests and other necessary checks.

Middleware

There needs to be some kind of story about how middleware gets done....

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.