Answering my own question to help others.
I'm not completely sure (because the Metaplex docs don't tell me, and other programs like SPL work on localhost) but Metaplex isn't available on localhost by default.
Saving the Token Metadata program for use on localhost
Find the address of the Token Metadata Program using https://explorer.solana.com/
Then in your shell:
# Token Metadata program
export METAPLEX_PROGRAM_ADDRESS="metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"
solana program dump -u m ${METAPLEX_PROGRAM_ADDRESS} metaplex_token_metadata_program.so
Then run:
solana program dump -u m ${METAPLEX_PROGRAM_ADDRESS} metaplex_token_metadata_program.so
Using a different network (that likely already has the Token Metadata Program loaded).
Modifying the Connection
above from:
const connection = new Connection("http://127.0.0.1:8899", "confirmed");
to:
const connection = new Connection(clusterApiUrl("devnet"), "confirmed");
Either method will now create an NFT:
{
response: {
signature: '4LtQJVaUbEFdj1hxfPtWsauzVmongHqi2ogLFoHNiMaC56EQjBoaAA8sFqxpadKFTRj8XE8DAkwpwvcaajLjPAbX',
confirmResponse: { context: [Object], value: [Object] }
},
mintAddress: PublicKey {
_bn: <BN: 5050e6a65c08671fa01e90c7ea6bb2deae4e0c6acbb2c4e42075c5cd16586dec>
},
metadataAddress: Pda {
_bn: <BN: 1fe9d690897e239152627074adc0c48dba4ce3d6b7e851bb5d6eccebf90033a9>,
bump: 251
},
masterEditionAddress: Pda {
_bn: <BN: f9ab790b1919e47e17b325515f57f5d6f6077e641cd4f2e69d648060db2931ae>,
bump: 255
},
tokenAddress: Pda {
_bn: <BN: 83bb07d2b1fb7041fcca1eedcc285c991eb40b5593dcc3ac948fa949d7b37a5f>,
bump: 255
},
nft: {
model: 'nft',
updateAuthorityAddress: PublicKey {
_bn: <BN: de3762527abedd0a802891770ee0a7306c04dba7c79620de328b03ff7902b90c>
},
json: { name: 'First NFT' },
jsonLoaded: true,
name: 'My NFT',
symbol: '',
uri: 'https://mockstorage.example.com/bV5x6x2y3dvQ5f29MTgV',
isMutable: true,
primarySaleHappened: false,
sellerFeeBasisPoints: 500,
editionNonce: 255,
creators: [ [Object] ],
tokenStandard: 0,
collection: null,
collectionDetails: null,
uses: null,
address: PublicKey {
_bn: <BN: 5050e6a65c08671fa01e90c7ea6bb2deae4e0c6acbb2c4e42075c5cd16586dec>
},
metadataAddress: Pda {
_bn: <BN: 1fe9d690897e239152627074adc0c48dba4ce3d6b7e851bb5d6eccebf90033a9>,
bump: 251
},
mint: {
model: 'mint',
address: [PublicKey],
mintAuthorityAddress: [PublicKey],
freezeAuthorityAddress: [PublicKey],
decimals: 0,
supply: [Object],
isWrappedSol: false,
currency: [Object]
},
token: {
model: 'token',
address: [Pda],
isAssociatedToken: true,
mintAddress: [PublicKey],
ownerAddress: [PublicKey],
amount: [Object],
closeAuthorityAddress: null,
delegateAddress: null,
delegateAmount: [Object],
state: 1
},
edition: {
model: 'nftEdition',
isOriginal: true,
address: [Pda],
supply: <BN: 0>,
maxSupply: <BN: 0>
}
}
}