Giter Club home page Giter Club logo

vaporcrudrouter's Introduction

CrudRouter

CrudRouter is a Rails-inspired extension to Vapor's routing system that makes it as simple as possible to set up CRUD (Create, Read, Update, Delete) routes for any Model. CrudRouter provides an API very similar to Rails' resources but with a few extra features including automatic responder generation and type safety.

Installation

Within your Package.swift

dependencies: [
    .package(url: "https://github.com/twof/VaporCRUDRouter.git", from: "1.0.0")
]

and

targets: [
    .target(name: "App", dependencies: ["CrudRouter"]),
]

Usage

Within your router setup (routes.swift in the default Vapor API template)

router.crud(register: Todo.self)

That's it!

That one line gets you the following routes.

GET     /todo       // returns all Todos
GET     /todo/:id   // returns the Todo with :id
POST    /todo       // create new Todo with provided body
PUT     /todo/:id   // update Todo with :id
DELETE  /todo/:id   // delete Todo with :id

Generated paths default to using lower snake case so for example, if you were to do

router.crud(register: SchoolTeacher.self)

you'd get routes like

GET     /school_teacher
GET     /school_teacher/:id
POST    /school_teacher
PUT     /school_teacher/:id
DELETE  /school_teacher/:id

Path Configuration

If you'd like to supply your own path rather than using the name of the supplied model, you can also do that

router.crud("account", register: User.self)

results in

GET     /account
GET     /account/:id
POST    /account
PUT     /account/:id
DELETE  /account/:id

Nested Relations

Say you had a model User, which was the parent of another model Todo. If you'd like routes to expose all Todos that belong to a specific User, you can do something like this.

router.crud(register: User.self) { controller in
    controller.crud(children: \.todos)
}

results in

GET     /user
GET     /user/:id
POST    /user
PUT     /user/:id
DELETE  /user/:id

GET     /user/:id/todo      // returns all Todos belonging to the User with :id
GET     /user/:id/todo/:id  // returns the Todo with :id belonging to the User with :id
POST    /user/:id/todo      // creates a new Todo belonging to the User with :id
PUT     /user/:id/todo/:id  // updates the Todo with :id belonging to the User with :id
DELETE  /user/:id/todo/:id  // deletes the Todo with :id belonging to the User with :id

within the supplied closure, you can also expose routes for related Parents and Siblings

controller.crud(children: \.todos)
controller.crud(parent: \.todos)
controller.crud(siblings: \.todos)

Including or Excluding Specific Routes

If you'd like to register a Model, but you don't want every route to be available, you can specify the ones you want, or exclude the ones you don't.

router.crud(register: Todo.self, .except([.create, .delete])) { controller in
    controller.crud(parent: \.owner, .only([.read]))
}

results in

PUT /todo/:id
GET /todo/:id
GET /todo

GET /todo/:id/tag/:id

Future features

  • query parameter support
  • PATCH support
  • automatically expose relations (blocked by lack of Swift reflection support)
  • generate models and rest routes via console command
  • Publicable support (potentially blocked by a compiler bug)
  • Fine grained per route public return models

vaporcrudrouter's People

Contributors

fedegens avatar simonedelmann avatar twof 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  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  avatar

vaporcrudrouter's Issues

API

Would you consider changing the API to:

try router.crud(register: User.self) { controller in
    try controller.crud(children: \.todos)
}

?

Unique property in Model

`final class Account: SQLiteModel {
var id: Int?

var username: String
var password: String
var avatar: String?

}`

Steps:

  1. I want to create a new account.
  2. Add router.crud(register: Account.self) in router.swift
  3. It always add successfully.
  4. But, username not check. I hope it is a unique property.
  5. How to check it ?

Granchild relationship repeats child id

I'm getting the following run-time error:

RoutingKit/TrieRouter.swift:215: Precondition failed: It is not possible to have two routes with the same prefix but different parameter names, even if the trailing path components differ (tried to add route with clubID that collides with eventID).

I'm probably doing something stupid here. I've a parent (Club), children (Event) and grandchildren (Table) - to represent a bridge club. Here's the model:

import Fluent
import Vapor

final class Club: Model, Content {
    static let schema = "club"
    
    @ID(key: .id)
    var id: UUID?
    
    @Field(key: "name")
    var name: String
    
    @Children(for: \.$club)
    var events: [Event]
    
    init() { }
    
    init(
        id: UUID? = nil,
        name: String
    ) {
        self.id = id
        self.name = name
    }
}

final class Event: Model, Content {
    static let schema = "event"
    
    @ID(key: .id)
    var id: UUID?
    
    @Parent(key: "club_id")
    var club: Club
    
    @Field(key: "date")
    var date: Date
    
    @Children(for: \.$event)
    var tables: [Table]
    
    init() { }
    
    init(
        id: UUID? = nil,
        clubID: Club.IDValue,
        date: Date
    ) {
        self.id = id
        self.$club.id = clubID
        
        self.date = date
    }
}

final class Table: Model, Content {
    static let schema = "table"
    
    @ID(key: .id)
    var id: UUID?
    
    @Parent(key: "event_id")
    var event: Event
    
    init() { }
    
    init(
        id: UUID? = nil,
        eventID: Event.IDValue
    ) {
        self.id = id
        self.$event.id = eventID
    }
}

Heres my routes.swift:

import Fluent
import Vapor
import CrudRouter

func routes(_ app: Application) throws {
    app.crud(register: Club.self) { router in
        router.crud(children: \.$events) { router in
            router.crud(children: \.$tables)
        }
    }
    
    for route in app.routes.all {
        print(route.description)
    }
}

I'm expecting the print out of the app.routes.all to generate something like:

PUT /club/:clubID
DELETE /club/:clubID
POST /club
GET /club
GET /club/:clubID
GET /club/:clubID/event
POST /club/:clubID/event
GET /club/:clubID/event/:eventID
DELETE /club/:clubID/event/:eventID
PUT /club/:clubID/event/:eventID
POST /club/:clubID/event/:eventID/table
DELETE /club/:clubID/event/:eventID/table/:tableID
GET /club/:clubID/event/:eventID/table/:tableID
GET /club/:clubID/event/:eventID/table
PUT /club/:clubID/event/:eventID/table/:tableID

But its actually generating:

PUT /club/:clubID
DELETE /club/:clubID
POST /club
GET /club
GET /club/:clubID
GET /club/:clubID/event
POST /club/:clubID/event
GET /club/:clubID/event/:eventID
DELETE /club/:clubID/event/:eventID
PUT /club/:clubID/event/:eventID
POST /club/:clubID/event/:clubID/table
DELETE /club/:clubID/event/:clubID/table/:tableID
GET /club/:clubID/event/:clubID/table/:tableID
GET /club/:clubID/event/:clubID/table
PUT /club/:clubID/event/:clubID/table/:tableID

Where the last five routes use the clubID for the event resource.

Children route appears as parent twice

Hi there. I stumbled across this project while trying to learn some Vapor :-) I did some rails a long time ago and was trying to figure out how to make the vapor router behave the way I wanted and your project seemed like just the thing.

However....

I have two models, Project and Issue.

Project contains

    var issues: Children<Project, Issue> {
        return children(\.projectId)
    }

Issue contains

    var project: Parent<Issue, Project> {
          return parent(\.projectId)
    }

My routing file contains

    router.crud(register: Project.self) { controller in
        controller.crud(children: \.issues)
    }

But the generates routes appear to be

+--------+----------------------------+
| POST   | /project/:int/project      |
+--------+----------------------------+
| GET    | /project/:int/project/:int |
+--------+----------------------------+
| GET    | /project/:int/project      |
+--------+----------------------------+
| DELETE | /project/:int/project/:int |
+--------+----------------------------+
| PUT    | /project/:int/project/:int |
+--------+----------------------------+

And I can't figure out what I'm doing wrong. Any help appreciated :-)

Custom decoding structs

It seems to be best practice not to decode a model directly but decoding a ModelCreate struct (or similar) instead. Although it is great to add crud functionality directly to a Model, what about adding (optional) protocols like below, so if someone wants to specify the requests (and also a public response)

protocol Createable {
    associatedtype Create: Content
    init(from data: Create) throws
}

Could be used like this:

extension User: Createable {
    struct Create: Content {
        var name: String
        var email: String
        var password: String
    }
    
    convenience init(from data: Create) throws {
        let password = try Bcrypt.hash(data.password)
        self.init(id: nil, name: data.name, email: data.email, password: password)
    }
}

Although this would add a lot of extra code for using CRUDRouter, this would make the package much more customizable. Also Patch requests could be implemented easily then, providing a custom struct with optional fields. And what about validation support?

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.