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

Is there any way to modularize routes? #677

Closed
PedroAugustoRamalhoDuarte opened this issue Oct 20, 2020 · 14 comments
Closed

Is there any way to modularize routes? #677

PedroAugustoRamalhoDuarte opened this issue Oct 20, 2020 · 14 comments

Comments

@PedroAugustoRamalhoDuarte

Hello!!
I am using mirageJs into my react native app to mock some externals apis calls in development. In this moment i have one big index.ts file, handling a lot of request from multiple apis, but that is not ideal. There is any way to build the routes with many files, like routes/external_api1.ts, and after import then in my server routes? Or there is any another way to do anything like that?

My index.ts that i want to split in api_auth and api_cep files, for example:

import { Server, Response } from 'miragejs';

export default function ({ environment = 'development' } = {}) {
  return new Server({
    environment,

    routes() {
      this.post('api_auth/signup', () => {
        return new Response(201, {}, userAuthResponse);
      });

      this.post('api_auth/login', () => {
        return new Response(201, {}, userAuthResponse);
      });

      this.post('api_cep/login', () => {
        return new Response(201, {}, cepResponse);
      });
    }
@samselikoff
Copy link
Contributor

There's a few ways you could do this – maybe after you find something you like we can add it to the docs!

First you could import functions and call them in your routes() hook:

import { createServer } from 'miragejs';
import apiAuthHandlers from './handlers/api-auth';
import apiCepHandlers from './handlers/api-cep';

export default function ({ environment = 'development' } = {}) {
  return createServer({
    environment,

    routes() {
      apiAuthHandlers(this)
      apiCepHandlers(this)
    }
  }
})

These could just be functions that take in the server instance (which is this within routes()) and define the handlers there:

// /mirage/handlers/api-auth.js
export default function(server) {
  server.post('api_auth/signup', () => {
    return new Response(201, {}, userAuthResponse);
  });

  server.post('api_auth/login', () => {
    return new Response(201, {}, userAuthResponse);
  }); 
}

I've done this before and it works great.

Alternatively you might be able to use the config() method on a server instance, which lets you augment an existing server with additional config. That could look something like this:

import { createServer } from 'miragejs';
import addApiAuthHandlers from './handlers/api-auth';
import addApiCepHandlers from './handlers/api-cep';

export default function ({ environment = 'development' } = {}) {
  let server = createServer({ environment });

  addApiAuthHandlers(server)
  addApiCepHandlers(server)

  return server;
})

Then those functions would look something like this:

// mirage/handlers/api-auth

export default function(server) {
  server.config({
    routes() {
      this.post('api_auth/signup', () => {
        return new Response(201, {}, userAuthResponse);
      });

      this.post('api_auth/login', () => {
        return new Response(201, {}, userAuthResponse);
      });
    }
  })
}

The nice thing about this approach is that because the other hooks are also available in the config() method, you could co-locate some related logic in these files, like adding more models or seeds to your server.

Let me know if one of those approaches works out!

@haluvibe
Copy link

The first method definitely works, I'm interested to know if the second method overrides the config with the passed object or merges the two?

@samselikoff
Copy link
Contributor

Second is supposed to merge. Pretty sure it does with routes() but not 100% on the other hooks. The original intention is that it was supposed to but I don't know if we ever got proper test coverage around it.

@haluvibe
Copy link

ok, I tried the first method, and it breaks in a weird way...

export const todosMirageRoutes = (server: Server): void => {
  server.get('/todos', (schema) => {
    return schema.todos.all()
  })

the above works, the below breaks...

export const todosMirageRoutes = (server: Server): void => {
  server.get('/todos', (schema) => {
    return schema.db.todos.all()
  })

Do you know why that might be?

@haluvibe
Copy link

I tried the second way, very quickly I end with a Typescript error...

Property 'config' does not exist on type 'Server<Registry<Record<string, ModelDefinition<{}>>, Record<string, FactoryDefinition<{}>>>>'.ts(2339)

export const addTodos = (server: Server) => {
  server.config({
    routes() {
      server.get('/todos', (schema) => {
        return schema.todos.all()
      })

      server.get('/todos/:id', (schema, request) => {
        return schema.todos.find(request.params.id)
      })

      server.get('/todos/delete/:id', (schema, request) => {
        return schema.todos.find(request.params.id).destroy()
      })
    },
  })
}

@samselikoff
Copy link
Contributor

Sorry there were some other folks who added the TS support so unfortunately I'm not the best person to help :( We have a Typescript channel in the Mirage discord that you might find more help on.

In the first comment, schema.todos.all() is referencing this API. schema.db.todos is a different thing. The first returns a Collection, the second returns a DbCollection. So you don't use all() on the db (the original idea was to make the db feel like a mongo db).

@samselikoff
Copy link
Contributor

These APIs are about 3-4 years old which is why there's so much friction with TS. I hope we can improve them after we clean up bugs and ship a v1.0 – I know TS is important to many people today and also many users of Mirage.

@PedroAugustoRamalhoDuarte
Copy link
Author

There's a few ways you could do this – maybe after you find something you like we can add it to the docs!

First you could import functions and call them in your routes() hook:

import { createServer } from 'miragejs';
import apiAuthHandlers from './handlers/api-auth';
import apiCepHandlers from './handlers/api-cep';

export default function ({ environment = 'development' } = {}) {
  return createServer({
    environment,

    routes() {
      apiAuthHandlers(this)
      apiCepHandlers(this)
    }
  }
})

These could just be functions that take in the server instance (which is this within routes()) and define the handlers there:

// /mirage/handlers/api-auth.js
export default function(server) {
  server.post('api_auth/signup', () => {
    return new Response(201, {}, userAuthResponse);
  });

  server.post('api_auth/login', () => {
    return new Response(201, {}, userAuthResponse);
  }); 
}

I've done this before and it works great.

Alternatively you might be able to use the config() method on a server instance, which lets you augment an existing server with additional config. That could look something like this:

import { createServer } from 'miragejs';
import addApiAuthHandlers from './handlers/api-auth';
import addApiCepHandlers from './handlers/api-cep';

export default function ({ environment = 'development' } = {}) {
  let server = createServer({ environment });

  addApiAuthHandlers(server)
  addApiCepHandlers(server)

  return server;
})

Then those functions would look something like this:

// mirage/handlers/api-auth

export default function(server) {
  server.config({
    routes() {
      this.post('api_auth/signup', () => {
        return new Response(201, {}, userAuthResponse);
      });

      this.post('api_auth/login', () => {
        return new Response(201, {}, userAuthResponse);
      });
    }
  })
}

The nice thing about this approach is that because the other hooks are also available in the config() method, you could co-locate some related logic in these files, like adding more models or seeds to your server.

Let me know if one of those approaches works out!

The first alternative works very well with TS, thanks you!! I believe that is a good idea to this example be in the docs !! 😄

@haluvibe
Copy link

yes, I agree the first alternative is great

@samselikoff
Copy link
Contributor

Awesome glad to hear! Created an issue for it here miragejs/site#1078

@TimRChen
Copy link

It's great. But why not put this practice in the official document ?

@hugo-petlove
Copy link

There's a few ways you could do this – maybe after you find something you like we can add it to the docs!

First you could import functions and call them in your routes() hook:

import { createServer } from 'miragejs';
import apiAuthHandlers from './handlers/api-auth';
import apiCepHandlers from './handlers/api-cep';

export default function ({ environment = 'development' } = {}) {
  return createServer({
    environment,

    routes() {
      apiAuthHandlers(this)
      apiCepHandlers(this)
    }
  }
})

These could just be functions that take in the server instance (which is this within routes()) and define the handlers there:

// /mirage/handlers/api-auth.js
export default function(server) {
  server.post('api_auth/signup', () => {
    return new Response(201, {}, userAuthResponse);
  });

  server.post('api_auth/login', () => {
    return new Response(201, {}, userAuthResponse);
  }); 
}

I've done this before and it works great.

Alternatively you might be able to use the config() method on a server instance, which lets you augment an existing server with additional config. That could look something like this:

import { createServer } from 'miragejs';
import addApiAuthHandlers from './handlers/api-auth';
import addApiCepHandlers from './handlers/api-cep';

export default function ({ environment = 'development' } = {}) {
  let server = createServer({ environment });

  addApiAuthHandlers(server)
  addApiCepHandlers(server)

  return server;
})

Then those functions would look something like this:

// mirage/handlers/api-auth

export default function(server) {
  server.config({
    routes() {
      this.post('api_auth/signup', () => {
        return new Response(201, {}, userAuthResponse);
      });

      this.post('api_auth/login', () => {
        return new Response(201, {}, userAuthResponse);
      });
    }
  })
}

The nice thing about this approach is that because the other hooks are also available in the config() method, you could co-locate some related logic in these files, like adding more models or seeds to your server.

Let me know if one of those approaches works out!

This was very helpful for me, too bad it's not in the documentation, would probably have saved a lot of my time.

@IanVS
Copy link
Collaborator

IanVS commented Feb 17, 2022

Would anyone like to open a PR for the docs?

@scottconso
Copy link

I did the second way because I need to modularize all config elements, not just routes. And it does work fully in environment 'test' - server.config() does seem to be additive for all the elements I am using - models, factories, serializers and routes - so I can add them piece-by-piece. And then when I call my seed data function in each test, it correctly seeds data using the models, factories, etc., and everything works. My basic steps are:

  1. Construct the server object (with just environment in the initial config) with createServer
  2. Call all my add....(server) functions to add my config elements piece-by-piece (no seeds part in the config)
  3. Call my seed...Data(server) function in each test to seed the data

But I have not been able to quite get it to work in environment 'development'. And it has to do with the fact that 'development' environment seems to require that the seed data function(s) be called through the seeds part of the config. Rather than after the server object is constructed.

So there ends up being a timing problem. The seeds part of the config seems to execute while the server object is being constructed, but at that time I have not yet added my config pieces such as models and factories, so the seed data comes out wrong.

So, I need to manually call my seed...Data(server) function after the server object is constructed, same as I do with 'test' environment, but that does not seem to be working. My seed data still comes out wrong, as if it still does not have visibility/access to the factories, even though by then they are there.

So, does anyone know a way to get this to work? With 'development' environment, to manually call seed...Data(server) function(s) after the server object is constructed and not use the seeds part of the config?

If there is not a way, could a fix be provided so that it could work this way? It seems like a sound concept - to be able to manually seed data after the server object is constructed, with any environment including 'development'.

So, my start server function is like:

export function startMocksServer({ environment = 'test' } = {}) {
  const server = createServer({
    environment,
  });

  addModels(server);
  addFactories(server);
  addSerializers(server);
  addRoutes(server);

  return server;
}

And I am trying to call it for 'development' like:

if (process.env.NODE_ENV === 'development') {
  const server = startMocksServer({ environment: 'development' });
  seedMockData(server);
}

Instead of the usual:

if (process.env.NODE_ENV === 'development') {
  startMocksServer({ environment: 'development' });
}

And, if I simply change environment: from 'development' to 'test', it does work, I just lose the console logging in the browser, and my response delays are shorter.

So, it seems tantalizingly close and as if it should work. Can you investigate and see if this could be made to work? (Seeding the data after-the-fact with any environment including 'development'.)

I am using version 0.1.46, which is the latest at the time of writing this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants