This repository demoes using a Vulcan Next frontend with a legacy Vulcan Meteor backend. This is targeted at existing Vulcan application that needs a progressive migration.
/!\ Please use Vulcan Next directly if you are not an experimented user of Vulcan Meteor
git clone https://github.com/VulcanJS/vulcan-meteor-next-transition
cd ./vulcan-next-transition
git submodule update --init --recursive
cd ./meteor-backend
# For contributions, go to the right branch (submodules only gets you to a commit)
# git checkout demo/with-next-frontend
# Install if not yet done:
meteor npm install
cp ./sample_settings.json ./settings.json
meteor npm run start -- --port=3001
# Remember to also read ./meteor-backend/README.md to continue the setup
cd ./next-frontend
# For contributions, go to the right branch (submodules only gets you to a commit)
# git checkout demo/with-meteor-backend
# Install if not yet done:
yarn install
yarn run dev
# Remember to also read ./next-frontend/README.md to continue the setup
The Next frontend will run on http://localhost:3000 ; the Meteor backend will run on http://localhost:3001. Note: you can still access the frontend part of your Meteor app as well! This is great if you want to keep the admin area in Meteor for instance, but move some pages to Next progressively.
You can authenticate in the Vulcan Next app as usual, using the same credentials you would use in Meteor.
Access the http://localhost:3000/meteor-demo to see the connection in action.
Top priority of this demo is to connect a Vulcan Next frontend to an existing Meteor backend.
Next.js Apollo client is configured to use Meteor's graphql endpoint, running on port 3001. This value is set in next-frontend/.env.development
.
Seed from Next.js is disabled in next-frontend/src/api/seed.ts
(we suppose you handle this in Meteor directly)
MONGO_URI is changed to connect to the Meteor DB. Technically, this is not necessary if you use Next only for frontend, but we still need this change this value to deactivate the demo mode that is automatically enabled when we detect you are using the demo database.
useUser hook will rely on Meteor useCurrentUser under the hood in next-frontend/src/components/user/hooks.ts
Changed next-frontend/src/pages/login.tsx
, next-frontend/src/pages/signup.tsx
and next-frontend/src/components/layout/Footer.tsx
to use legacy hooks to connect to Meteor. NOTE: in a future iteration, we should abstract the Next.js version into hooks, to make this cleaner
Define Apollo CORS so we can include crendentials in meteor-backed/settings.json
Setup CORS cookies in the Next frontend in next-frontend/packages/@vulcanjs/next-apollo/apolloClient.ts
The meteor-backend/packages/getting-started/lib/modules/vulcanResource/vulcanResource.js
file contains common parts, ie the schema. It is reused to build the Vulcan Meteor collection, as well as the equivalent Vulcan Next model (model is the new terms for collection, and it's way more powerful, but only available in Next at the moment).
We cannot put this folder as the root, as Meteor cannot import file outside of the package. So it has to live in Meteor until we find a better approach for the common
folder.
Automated emails (eg for mail verification after signup) needs to point to the Next frontend, even if they are triggered by the Next.js backend. If you use those features, you'll need to add this piece of code in the Meteor app:
Meteor.startup(() => {
Accounts.urls.resetPassword = function(token) {
// where localhost:3000 is your Next.js app
return 'http://localhost:3000/reset-password/' + token;
};
(TODO: we should add this feature in this repo and make it more configurable based on a Meteor setting)
Replace "Link" from "react-router-dom" by Link from Next.js https://nextjs.org/docs/api-reference/next/link
In the long run, the goal is to transition the Meteor backend to Next as well, using Next API routes.
Next is configured to use the same Mongo database as Meteor locally. To get the URL of the local Meteor database, run meteor mongo -U
. It's most probably something like this: mongodb://127.0.0.1:3002/meteor
You can use both Next's GraphQL API and Meteor's GraphQL API using the Connect to multiple graphql API in the frontend pattern described here
See this script: https://github.com/GraemeFulton/vulcan-next-meteor-mongo-migrate
See https://github.com/VulcanJS/vulcan-npm/issues/63.
Meteor uses string _id
as a default, while Mongo uses ObjectId
type, so you need to do ObjectId.str
to get the actual id or use a method like isEqual
from lodash. This means that objects created using Next backend might create bugs in the Meteor backend. You need to avoid that, by doing creation operations only in Meteor, or to clearly separate things you manage in Next and things you manage in Meteor until you have fully transitionned.
_id
to an ObjectId
, see https://forums.meteor.com/t/convert-meteor-mongo-string--id-into-objectid/19782.
You can change the _id
generation scheme (see https://docs.meteor.com/api/collections.html) to ObjectId in the Meteor app if you need a progressive transition.
TODO: We still have an issue with function convertIdAndTransformToJSON<TModel>
, it tries to access document._id
for string conversion but it seems to be undefined when using a Meteor Mongo database.
Current workaround:: clone users manually in Compass, it will recreate an id as an ObjectID https://docs.mongodb.com/compass/current/documents/clone/
Better workaround: force mongoose to use string ids in the Vulcan Next model https://forums.meteor.com/t/using-mongoose-to-query-a-mongo-db-that-was-from-a-meteor-db/53789
There is an example "user.server" model in Vulcan NextSimply alter Vulcan Next password check method so it fall back to services.password.bcrypt
to find the hashed password, see https://willvincent.com/2018/08/09/replicating-meteors-password-hashing-implementation/
This is already done in Vulcan Next latest version.
Example structure of a user:
{
"_id":"RXSk3HJemC6haii64",
"username":"administrator",
"email":"admin@vulcanjs.org",
"isDummy":false,
"groups":["admins"],
"isAdmin":true,
"emails":[{"address":"admin@vulcanjs.org"}],
"createdAt":{"$date":"2021-12-29T11:43:29.772Z"},
"displayName":"administrator",
"slug":"administrator",
// admin123
"services": {
"password": {
"bcrypt": "$2b$10$vxNlPs/AJdIYlypKCp7RVOiFNcc0I8ay6xj0TTXnA94rh5SSn.I8W"
}
"status":{"online":false}}
Example code:
export const checkPasswordForUser = (
user: Pick<UserTypeServer, "hash" | "salt">,
passwordToTest: string
): boolean => {
//legacy meteor start
const userInput = crypto
.Hash('sha256')
.update(passwordToTest)
.digest('hex')
if(bcrypt.compareSync(userInput, `$2b$10$${user.salt}${user.hash}`)){
console.log('match')
const passwordsMatch = user.hash;
return passwordsMatch;
}//legacy meteor end
const hash = (crypto as any)
.pbkdf2Sync(userInput, user.salt, 1000, 64, "sha512")
.toString("hex");
const passwordsMatch = user.hash === hash;
return passwordsMatch;
};
/!\ This doesn't work as expected, probably because Meteor is using SHA256 and Vulcan Next SHA512. Instead, update the password field only when the user change their password.
To update password:
services.password.bcrypt
salt
. The rest is the hash
. You can ignore the beginning.
Example: $2b$10$abcdefghijklmnopqrstuvWXYZ123456789
=> salt
is abcdefghijklmnopqrstuv
and hash
is WXYZ12345678
hash
in hash
and salt
in salt
in the user document.