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

[cli] ability to generate RESTful routes #65

Open
HashanCP opened this issue Apr 1, 2021 · 12 comments
Open

[cli] ability to generate RESTful routes #65

HashanCP opened this issue Apr 1, 2021 · 12 comments
Labels
cli Issue related to the CLI enhancement New feature or request

Comments

@HashanCP
Copy link

HashanCP commented Apr 1, 2021

Hi Johan,

creating files over and over can be tedious over time. So as a start, a common use case when designing APIs is ability to generate RESTful routes as below

restapify generate dogs

will generate following files in current directory

├── dogs
│   ├── [dogId]
│   │   ├── _.DELETE.json
│   │   ├── _.GET.json
│   │   ├── _.PATCH.json
│   │   └── _.PUT.json
│   ├── _.GET.json
│   └── _.POST.json

resulting in following routes

GET    /api/dogs
POST   /api/dogs
GET    /api/dogs/:id
PUT    /api/dogs/:id
PATCH  /api/dogs/:id
DELETE /api/dogs/:id

We can put default(opinionated) content into generate files according best practices in the industry. In my opinion this would give user a quick starting point and improved DX.

There are similar implementations of developer tooling in the world of Ruby on Rails, Laravel although i am not aware of the exact commands to do so. Anyway, this is an abstract idea just for now. It should be possible(in future) to create error states via cli when passed as arguments/flags such as

restapify generate dogs --error=404:NOT_FOUND --error=400:BAD_REQUEST

what is you opinion on implementing such a feature?

cheers

@johannchopin
Copy link
Owner

Hey @HashanCP 👋

I really like this idea 👍 it will definitively be a new feature to implement. Have you some CLI examples that generate files (I guess laravel and angular have popular CLI that works in the same way) to estimate what could be the best DX for such a command?

@HashanCP
Copy link
Author

HashanCP commented Apr 1, 2021

@johannchopin

there are few i found while investigating the idea. But since rails and laravel are MVC-based in both tools its its bound to 'Controller'. Regardless

php artisan generate DogController --resource

Note that, in Artisan, --resource flag will add relevant routes to routes file in Laravel. They call this idea 'Resourceful Routing'. Like i said since the nature of MVC frameworks, there are additional (GET)routes to facilitate 'View' layer. Anyway, to find out more Generate CRUD Routes

In rails its bit weird, command will be rails g scaffold_controller Property as our friend StackOverflow suggest and has the same effect.

I guess when it comes to restapify, due to its file-based routing, idea of generating RESTful routes is the important bit. I feel like, we can decide on a much better cli interface for restapify.

@HashanCP
Copy link
Author

HashanCP commented Apr 1, 2021

In Laravel,
php artisan make:controller PhotoController --api

can be used to create just only "api routes" that create all routes except 'create' and 'edit' (GET)methods. This is the kind of output more suitable for restapify i guess. More on Laravel Official Doc

@johannchopin
Copy link
Owner

Thanks for the research 👍 Yeah I really like your initial idea since it's only about REST routes and nothing else.

So we can imagine that restapify generate dogs will generate:

├── dogs
│   ├── _.GET.json
│   └── _.POST.json

and restapify generate dogs/[dogId] generate:

├── dogs
│   ├── [dogId]
│   │   ├── _.DELETE.json
│   │   ├── _.GET.json
│   │   ├── _.PATCH.json
│   │   └── _.PUT.json

So that the CLI only generate what the user ask for.

Then we could add some flags like --no-patch, --no-delete to disable some method and also maybe --states 404:NOT_FOUND 400:BAD_REQUEST to generate extra routes with state. What do you think?

@HashanCP
Copy link
Author

HashanCP commented Apr 2, 2021

hi @johannchopin

i made a small shell script to get a feel for a potential CLI for restapify. I created bash script(assuming a *nix system) in a Gist. I've put your ideas into the mix as well.

Syntax
restapify <command> <entity> [flgs...]

There will be 2 new commands. generate and resource. By default generate command will generate only GET route for specified entity.

generate command

restapify generate dogs

├── dogs
│   ├── _.GET.json

restapify generate dogs/:id

├── dogs
│   ├── [dogId]
│   │   ├── _.GET.json

real power of this comes with certain flags,

  • -m - minimal routes will generate POST routes additionally
  • -r - resource routes will generate all POST, PATCH, PUT, DELETE routes
  • -s <state_string> - will generate common states(such as 400, 404) for specified entity.
  • -e <state_string> - will generate an extended set(401, 500, 204 etc) of states (i know its crazy 😜, 40+ files)

Note that state_string is of format 4xx, 5xx to conveniently represent HTTP state codes lets look at some examples. -s and -e flags are bit smart since they only create states for the available routes in that directory.

restapify generate dogs -m

├── dogs
│   ├── _.GET.json
│   └── _.POST.json

restapify generate dogs/:id -r

├── dogs
│   └── [id]
│       ├── _.DELETE.json
│       ├── _.GET.json
│       ├── _.PATCH.json
│       ├── _.POST.json
│       └── _.PUT.json

restapify generate dogs/:id -r -s 4xx will create an opinionated list of HTTP 4xx codes for

├── dogs
│   └── [id]
│       ├── _.DELETE.404.{NOT_FOUND}.json
│       ├── _.DELETE.json
│       ├── _.GET.400.{BAD_REQUEST}.json
│       ├── _.GET.json
│       ├── _.PATCH.400.{BAD_REQUEST}.json
│       ├── _.PATCH.404.{NOT_FOUND}.json
│       ├── _.PATCH.json
│       ├── _.POST.400.{BAD_REQUEST}.json
│       ├── _.POST.json
│       ├── _.PUT.400.{BAD_REQUEST}.json
│       ├── _.PUT.409.{CONFLICT}.json
│       └── _.PUT.json

In the Gist i created all stuff, but the idea is user can choose what HTTP states he/she wants via state_string. For example if user wants 2xx and 5xx codes can do -s 2xx,5xx and cli will generate a small set of common states falls into the specified category. Generating an extended set of states(in turn creates bunch of files) can be a least priority, but it felt a nice to have. So here it is

restapify generate dogs/:id -e 2xx,4xx,5xx

├── dogs
│   └── [id]
│       ├── _.DELETE.204.{NO_CONTENT}.json
│       ├── _.DELETE.401.{UNAUTHORIZED}.json
│       ├── _.DELETE.403.{FORBIDDEN}.json
│       ├── _.DELETE.404.{NOT_FOUND}.json
│       ├── _.DELETE.409.{CONFLICT}.json
│       ├── _.DELETE.500.{INTERNAL_SERVER_ERROR}.json
│       ├── _.DELETE.json
│       ├── _.GET.400.{BAD_REQUEST}.json
│       ├── _.GET.401.{UNAUTHORIZED}.json
│       ├── _.GET.500.{INTERNAL_SERVER_ERROR}.json
│       ├── _.GET.json
│       ├── _.PATCH.204.{NO_CONTENT}.json
│       ├── _.PATCH.400.{BAD_REQUEST}.json
│       ├── _.PATCH.401.{UNAUTHORIZED}.json
│       ├── _.PATCH.403.{FORBIDDEN}.json
│       ├── _.PATCH.404.{NOT_FOUND}.json
│       ├── _.PATCH.500.{INTERNAL_SERVER_ERROR}.json
│       ├── _.PATCH.json
│       ├── _.POST.201.{CREATED}.json
│       ├── _.POST.400.{BAD_REQUEST}.json
│       ├── _.POST.401.{UNAUTHORIZED}.json
│       ├── _.POST.403.{FORBIDDEN}.json
│       ├── _.POST.409.{CONFLICT}.json
│       ├── _.POST.500.{INTERNAL_SERVER_ERROR}.json
│       ├── _.POST.json
│       ├── _.PUT.204.{NO_CONTENT}.json
│       ├── _.PUT.400.{BAD_REQUEST}.json
│       ├── _.PUT.401.{UNAUTHORIZED}.json
│       ├── _.PUT.403.{FORBIDDEN}.json
│       ├── _.PUT.409.{CONFLICT}.json
│       ├── _.PUT.500.{INTERNAL_SERVER_ERROR}.json
│       └── _.PUT.json

resource command

Last but not least resource command, will generate Resourceful Routing files with a single command. One can think that this command is more of a syntactic sugar over generate

restapify resource dogs

├── dogs
│   ├── _.GET.json
│   ├── [id]
│   │   ├── _.DELETE.json
│   │   ├── _.GET.json
│   │   ├── _.PATCH.json
│   │   ├── _.POST.json
│   │   └── _.PUT.json
│   └── _.POST.json

It also supports setting HTTP states if user wishes so and will create necessary HTTP states. This is fully not working in the Gist i provided. Ideally it will be,

restapify resource dogs -s 4xx,2xx

├── dogs
│   ├── _.GET.400.{BAD_REQUEST}.json
│   ├── _.GET.json
│   ├── [id]
│   │   ├── _.DELETE.404.{NOT_FOUND}.json
│   │   ├── _.DELETE.json
│   │   ├── _.GET.400.{BAD_REQUEST}.json
│   │   ├── _.GET.json
│   │   ├── _.PATCH.400.{BAD_REQUEST}.json
│   │   ├── _.PATCH.404.{NOT_FOUND}.json
│   │   ├── _.PATCH.json
│   │   ├── _.POST.201.{CREATED}.json
│   │   ├── _.POST.400.{BAD_REQUEST}.json
│   │   ├── _.POST.json
│   │   ├── _.PUT.400.{BAD_REQUEST}.json
│   │   ├── _.PUT.409.{CONFLICT}.json
│   │   └── _.PUT.json
│   ├── _.POST.201.{CREATED}.json
│   ├── _.POST.400.{BAD_REQUEST}.json
│   └── _.POST.json

Things to consider

  • File Content - we have to do some research on what content should be put into generated files by default based on industry's best practices
  • Entity Name - Use singular nouns when generating routes. For example restapify generate dog will generate /dogs route. As little as it might seem, i've seen many CLI tooling adapt this idea(Laravel included) and let the tool take care of pluralization.

Also i've found new potential features for restapify which i will raise as new issues for further discussion later 😉

Let me know what you think 😁

@johannchopin
Copy link
Owner

Hey @HashanCP this Is really awesome and like the idea of the -r flag.

Concerning the states there is a little miscomprehension. A state in Restapify can be just a simple string and not necessary a HTTP status code identifier so you could have a state _.DELETE.404.{INV_ID} for example.

I also figured out that to make this command work, user need to have a restapify.config.json so that the cli now what the rootDir is.

Thanks a lot for your interest and time that you spend on this project 💪

@HashanCP
Copy link
Author

HashanCP commented Apr 2, 2021

Concerning the states there is a little miscomprehension. A state in Restapify can be just a simple string and not necessary a HTTP status code identifier so you could have a state _.DELETE.404.{INV_ID} for example.

yes, you are right...i made a mistake in the script and last comment...updated both accordingly 😄

looking forward for your input about further implementation

cheers

@johannchopin
Copy link
Owner

@HashanCP would you be interrested to implement this feature in the project if you have time ? 👍

@HashanCP
Copy link
Author

HashanCP commented Apr 3, 2021

sure @johannchopin, happy to 🤩. i will post exact implementation details after going through current codebase. Feel free to suggest any additional features you'd want in this implementation. 😉

@johannchopin
Copy link
Owner

johannchopin commented Apr 3, 2021

Perfect so lets discuss the implementation of it 👍

So it can be used like :

restapify generate dogs
# or restapify generate dog
restapify generate dogs/[dogId]

Which will create only the GET endpoints.

First we need to verify that there is a config file restapify.conf.json at the root or an error is displayed.

I used commanderjs for the cli which is pretty straightforward, you can find some implementation of it in cli.ts file.

This command will have several flags:

-r

It creates specific routes and states like:

restapify generate dog -r POST:202 DELETE DELETE:404:INV_ID

It will accept routes in following format

  1. GET
  2. POST:201
  3. PATCH:400:INV_DATA
  4. minimal
  5. resource

For now, I guess its ok to populate the file with an empty array for routes like /dogs. For a route like /dogs/[dogId] we can populate the file with someone like:

{
  "dogId": "[dogId]"
}

If you have some other ideas or something to discuss feel free to post your it and I will update this post which will be the 'wiki' for the creation of this new feature.

Thanks again for your help 😉

@johannchopin johannchopin added cli Issue related to the CLI enhancement New feature or request labels Apr 3, 2021
@HashanCP
Copy link
Author

HashanCP commented Apr 3, 2021

@johannchopin i played with cli code and thought of a more concise interface for it.

restapify generate dogs
restapify generate dogs/[dogId]

couple of things to note here that will be in final implementation for above commands

  1. Use dog singular noun for the route and CLI will take care of pluralization accordingly
restapify generate dog ---> /dogs
  1. Use express-like route params and conversion is taken care by CLI along with pluralization. dog/:id -> dogs/[dogId]. One thing to also note is /dog/:id will converted into [dogId] so if user ask for,
restapify generate dog/:code ---> dog/[dogCode]

In my opinion this adds generating routes via CLI bit smart and much friendly for adoption due to express-like passed over cli. And as discussed issuing these commands will ONLY generate GET endpoints

This command will have several flags:

-m: generate also the POST file
-r: generate all files for each method
--no-delete, --no-put, ...: don't generate a file for a specific method
-s <states>: generate states file. Ex: -s POST.404.INV_ID DELETE:500:SERVER_CRASH

Now for flags, I'd like to propose a change to flags which are different from our initial discussion but will address above concerns. Let me break it down(in terms of commanderjs).

--methods flag

-m, --methods <methods...> will generate routes mentioned in methods array. For example

restapify generate dog -m POST PUT

│   ├── dogs
│   │   ├── _.GET.json  ### default
│   │   ├── _.POST.json
│   │   └── _.PUT.json

In addition to HTTP methods, --methods will accept reserved strings minimal and resource

restapify generate dog -m resource

├── dogs
│   ├── _.GET.json
│   ├── [dogId]
│   │   ├── _.DELETE.json
│   │   ├── _.GET.json
│   │   ├── _.PATCH.json
│   │   ├── _.POST.json
│   │   └── _.PUT.json
│   └── _.POST.json

restapify generate dog -m minimal

├── dogs
│   ├── _.GET.json
│   └── _.POST.json

restapify generate dog/:id -m minimal

├── dogs
│   ├── [dogId]
│   │   ├── _.DELETE.json
│   │   ├── _.GET.json
│   │   ├── _.PATCH.json
│   │   ├── _.POST.json

here minimal argument behave differently based on provided route. if route is top-level it will create GET & POST only. if route accepts a parameter(most likely a CRUD route) it will generate GET, POST, PATCH and DELETE. Let me know ur view on this 😄

--states flag

-x, --states <states...> will generate requested states in states array. I thought to use -x short format for --states flag cuz it sounds similar 😉 but we can rollback to -s if you want. I think its better we generate states ONLY for methods that are already available in the directory. This way we can get rid of specifying HTTP method when generating states and reduce number of files(and key strokes too 😃 )

state can be of following format

  • 400 - generate only 400 state with a known HTTP state (in this case BAD_REQUEST)
  • 401:NO_ENTRY - generate 401 state with user given NO_ENTRY state
  • 2xx - generate known set of 2xx states for appropriate methods (201 for POST, 204 DELETE request like so)
  • 4XX - generate an extended set of known states for appropriate methods (401 for GET, 400 GET, 409 for PUT, 404 DELETE etc)

NOTE: In _xx and _XX (uppercase X) values user won't be able to mention a defined state, instead CLI will use a known HTTP state based on the code.

Lets see few examples

restapify generate dog -x 400

├── dogs
│   ├── _.GET.json
│   └── _.GET.400.{BAD_REQUEST}.json

restapify generate dog -x 401:NO_ENTRY

├── dogs
│   ├── _.GET.json
│   └── _.GET.401.{NO_ENTRY}.json

restapify generate dog -x 2xx 4xx

├── dogs
│   ├── _.GET.400.{BAD_REQUEST}.json
│   ├── _.GET.json
│   ├── _.POST.201.{CREATED}.json
│   ├── _.POST.400.{BAD_REQUEST}.json
│   └── _.POST.json

restapify generate dog -x 401:NO_ENTRY 2xx 5XX

├── dogs
│   ├── _.GET.400.{BAD_REQUEST}.json
│   ├── _.GET.500.{INTERNAL_SERVER_ERROR}.json
│   ├── _.GET.json
│   ├── _.POST.201.{CREATED}.json
│   ├── _.POST.400.{BAD_REQUEST}.json
│   ├── _.POST.401.{NO_ENTRY}.json
│   ├── _.POST.403.{FORBIDDEN}.json
│   ├── _.POST.409.{CONFLICT}.json
│   ├── _.POST.500.{INTERNAL_SERVER_ERROR}.json
│   └── _.POST.json

For now, I guess its ok to populate the file with an empty array for routes like /dogs. For a route like /dogs/[dogId] we can populate the file with someone like:

As for content, i agree with you. I can do a bit of research what would be good defaults for content, but i suspect it would be bit hard rather opinionated to generate a generalized content. But for now, empty content would be good.

So I guess above changes would be a more balanced, modular outcome and a smooth DX based on my experiments so far. Let me know what you think of this kind of an interface. 😏

cheers

Repository owner deleted a comment from HashanCP Apr 3, 2021
@johannchopin
Copy link
Owner

Hey @HashanCP 👋 Just ping me if you need some help of any kind. Also if you had some other issues feel free to post them I would have time this week 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cli Issue related to the CLI enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants